3. 传感器数据接入与协议解析
在农业自动化系统中,土壤湿度传感器通常部署在田间或温室的不同点位,通过无线网关以 MQTT 协议周期性上报数据。Node-RED 作为流程编排层,第一步要做的不是控制灌溉,而是把“原始报文”变成“可用的数据对象”。本节围绕这一环节展开:用 MQTT 输入节点订阅传感器主题,解析 JSON 载荷,提取关键字段,并输出结构统一、单位明确、可直接被后续判断节点消费的规范化消息。
一、先明确数据契约,再动手连线
很多初学者拿到传感器就直接拖 MQTT 节点、连 debug 节点看输出,结果字段名一变、单位一换,后面的流程全部失效。正确做法是先定义“规范化后的数据契约”。假设我们使用的土壤湿度传感器上报如下 JSON 载荷(示例数据,用于教学演示):
{
"devId": "SM-01",
"ts": 1717488000,
"battery": 87,
"soil": {
"moisture": 23.6,
"temp": 21.4,
"ec": 1.28
},
"status": "ok"
}
这里有几个需要提前约定的点:soil.moisture 是体积含水率,单位 %;ts 是秒级 Unix 时间戳;devId 是设备编号。我们约定规范化后的输出对象为:deviceId(字符串)、soilMoisture(数值,%)、soilTemp(数值,℃)、battery(数值,%)、timestamp(毫秒时间戳)、valid(布尔)。字段名统一采用驼峰式,时间统一为毫秒,这样后续的阈值判断、存储和看板都不必再关心原始报文结构。
二、MQTT 输入节点的关键配置
在 Node-RED 中拖入一个 mqtt in 节点,双击配置。服务器指向本地或网关的 Broker,例如 mqtt://192.168.1.10:1883;主题填写 agri/soil/+/data,其中 + 是单层通配符,可同时匹配 agri/soil/SM-01/data、agri/soil/SM-02/data 等多个点位,便于后续按设备扩展。QoS 建议选 1,保证至少一次送达;输出类型保持“自动检测(auto-detect)”,Node-RED 会尝试把 JSON 字符串解析为对象。
需要特别提醒:不要过度依赖 auto-detect。当载荷是合法 JSON 时它确实能解析,但一旦网关发送的是带前后空白的字符串、或载荷被包在数组里,自动解析就可能失败,msg.payload 仍是字符串。因此更稳妥的做法是把输出设为“字符串”,随后接一个 json 节点显式解析,解析失败时节点会抛出错误,便于用 catch 节点统一处理。
三、解析与字段提取的实现
解析之后,用 function 节点完成字段提取与规范化。核心逻辑是“取值—校验—转换—重组”,而不是简单赋值:
const raw = msg.payload;
if (!raw || !raw.soil) {
node.warn("载荷缺少 soil 字段,丢弃");
return null;
}
const m = Number(raw.soil.moisture);
const t = Number(raw.soil.temp);
const valid = Number.isFinite(m) && m >= 0 && m <= 100;
msg.payload = {
deviceId: raw.devId || "unknown",
soilMoisture: valid ? m : null,
soilTemp: Number.isFinite(t) ? t : null,
battery: Number(raw.battery),
timestamp: (raw.ts || Math.floor(Date.now() / 1000)) * 1000,
valid: valid
};
return msg;
这段代码做了四件事:第一,防御性判断,缺少 soil 直接返回 null 丢弃消息,避免脏数据污染下游;第二,用 Number() 强制转换,防止传感器把数值发成字符串;第三,用 Number.isFinite 和 0~100 的范围校验判断湿度是否可信,超出物理范围的数据标记为无效;第四,把秒级时间戳乘以 1000 转为毫秒,并在缺失时用当前时间兜底。注意 return null 在 Node-RED 的 function 节点中表示不输出消息,这是丢弃异常数据的标准写法。
soil.moisture 与 sm)。此时可在 function 节点内做“字段映射表”,按 devId 前缀选择解析分支,而不是为每种型号各建一条流程,便于维护。四、从原始报文到规范化数据的处理链
下图展示了本节完整的数据处理链:MQTT 输入节点订阅通配主题,json 节点显式解析,function 节点完成校验与字段重组,最终输出统一契约的消息,异常分支由 catch 节点接管。
五、结果观察与判断依据
配置完成后点击部署,在 MQTT 输入节点后接一个 debug 节点,把输出设为“完整消息对象”。当传感器上报时,调试面板应看到形如 {"deviceId":"SM-01","soilMoisture":23.6,"soilTemp":21.4,"battery":87,"timestamp":1717488000000,"valid":true} 的对象。判断接入是否成功的依据有三条:一是字段名与契约完全一致,没有残留 soil.moisture 这类嵌套路径;二是 soilMoisture 是数值类型而非字符串,可在 debug 中通过颜色区分;三是 timestamp 为 13 位毫秒值。
还可以做一次“坏数据注入”验证:用 MQTT 客户端向 agri/soil/SM-01/data 手动发布 {"devId":"SM-01","soil":{"moisture":"abc"}},预期 function 节点输出 valid:false 且 soilMoisture:null,而不是让流程崩溃。再发布一段非 JSON 文本,预期 json 节点报错并被 catch 节点捕获。这两次验证能确认解析链的健壮性,也是后续做阈值告警和灌溉决策的前提。
msg.payload 做 JSON.parse,当载荷已被自动解析为对象时会二次解析报错。二者只选其一,推荐“字符串输出 + json 节点显式解析”的组合,错误边界更清晰。完成本节后,流程中流动的已经是带设备标识、单位明确、有效性可判的规范化数据。后续的湿度阈值判断、灌溉阀控制、历史数据存储,都可以直接基于 msg.payload.soilMoisture 与 msg.payload.valid 编写,而不必再回头处理原始报文的差异。