跳转到内容

条件控制逻辑

条件控制逻辑

本节介绍在投料系统中实现条件控制逻辑的方法——即在特定条件下覆盖默认的时间调度,实现更智能的投料控制。学习完成后,您将能够:

  • 设计液位触发的条件投料逻辑
  • 实现多条件组合决策
  • 配置安全保护条件(缺水断电等)
  • 通过 Node-RED Dashboard 动态调整条件参数
┌──────────────────────────────────────────────────────────────┐
│ 条件控制决策树 │
├──────────────────────────────────────────────────────────────┤
│ │
│ 输入: │
│ - 时间调度结果 (投料/不投料) │
│ - 当前液位 (%) │
│ - 上次投料时间 │
│ - 液位变化趋势 │
│ │
│ 条件判断 (按优先级): │
│ │
│ 1. 安全条件: │
│ 液位 < 5% → 禁止投料 (缺水,泵会空转) │
│ │
│ 2. 低液位紧急投料: │
│ 液位 < 20% AND 超过最小间隔 → 强制投料 │
│ │
│ 3. 满液位跳过投料: │
│ 液位 > 90% → 跳过本次计划投料 │
│ │
│ 4. 时间调度结果: │
│ 正常投料/不投料 (默认) │
│ │
│ 输出: │
│ - 最终指令 (投料/不投料) │
│ - 决策原因 (供日志和 Dashboard 显示) │
│ │
└──────────────────────────────────────────────────────────────┘
// Function: 条件控制逻辑
// 输入: msg.payload.command (时间调度结果 "1" 或 "0")
// + flow.get("lastSensorData") (当前传感器数据)
// 输出: 覆盖后的最终指令
// ===== 读取传感器数据 =====
var sensorData = flow.get("lastSensorData") || {};
var levelPercent = sensorData.level || 0; // 当前液位 (%)
var distance = sensorData.distance || 0; // 当前距离 (cm)
// ===== 配置条件参数 =====
var conditions = flow.get("dosingConditions") || {
// 安全保护
emptyLevel: 5, // 缺水阈值 (%)
emptyAction: "inhibit", // inhibit=禁止, warn=仅警告
// 紧急投料
lowLevel: 20, // 低液位阈值 (%)
lowMinInterval: 7200, // 紧急投料最小间隔 (秒, 2 小时)
// 满液位跳过
fullLevel: 90, // 满液位阈值 (%)
skipFull: true, // 满液位是否跳过投料
// 趋势判断
trendEnabled: true, // 启用趋势分析
rapidDropThreshold: 10, // 快速下降阈值 (% 每次唤醒)
rapidDropAction: "force" // force=强制投料, warn=警告
};
// ===== 时间调度结果 =====
var scheduledCommand = msg.payload || "0";
var finalCommand = scheduledCommand;
var decisionReason = "time_schedule";
var overridden = false;
// ===== 条件 1: 安全保护 - 缺水 =====
if (levelPercent < conditions.emptyLevel &&
levelPercent >= 0) { // 排除传感器故障 (-1)
finalCommand = "0"; // 强制不投料
decisionReason = "empty_container";
overridden = true;
node.warn("SAFETY: Container nearly empty (" + levelPercent +
"%), dosing inhibited");
}
// ===== 条件 2: 低液位紧急投料 =====
if (levelPercent < conditions.lowLevel &&
levelPercent >= conditions.emptyLevel &&
scheduledCommand === "0") {
// 检查是否达最小间隔
var lastDosingTime = flow.get("lastDosingTimestamp") || 0;
var currentTime = Math.floor(Date.now() / 1000);
var timeSinceLastDose = currentTime - lastDosingTime;
if (timeSinceLastDose >= conditions.lowMinInterval) {
finalCommand = "1"; // 强制投料
decisionReason = "low_level_emergency";
overridden = true;
node.log("EMERGENCY: Low level (" + levelPercent +
"%), forcing dosing");
}
}
// ===== 条件 3: 满液位跳过投料 =====
if (conditions.skipFull &&
levelPercent > conditions.fullLevel &&
scheduledCommand === "1") {
finalCommand = "0";
decisionReason = "full_container_skip";
overridden = true;
node.log("SKIP: Container full (" + levelPercent +
"%), skipping scheduled dosing");
}
// ===== 条件 4: 液位快速下降检测 =====
if (conditions.trendEnabled) {
var previousLevel = flow.get("previousLevel") || levelPercent;
var levelDrop = previousLevel - levelPercent;
if (levelDrop >= conditions.rapidDropThreshold) {
// 液位快速下降 → 可能泄漏
node.warn("RAPID DROP: Level dropped " + levelDrop +
"% since last check");
// 发送告警而非修改投料
flow.set("rapidDropAlert", {
drop: levelDrop,
timestamp: Date.now(),
fromLevel: previousLevel,
toLevel: levelPercent
});
}
// 保存当前液位供下次比较
flow.set("previousLevel", levelPercent);
}
// ===== 构建结果 =====
var result = {
command: finalCommand,
scheduled: scheduledCommand,
overridden: overridden,
reason: decisionReason,
conditions: {
level: levelPercent,
emptyThreshold: conditions.emptyLevel,
lowThreshold: conditions.lowLevel,
fullThreshold: conditions.fullLevel
}
};
// 保存决策记录
flow.set("lastConditionalResult", result);
// 日志输出
if (overridden) {
node.log("Override: " + scheduledCommand + "" + finalCommand +
" (" + decisionReason + ")");
}
// 输出最终指令
msg.payload = result.command;
msg.topic = "esp32/dosing/control";
msg.metadata = result;
return msg;
// Function: 保存 Dashboard 条件配置
// 接收来自 Dashboard 表单的输入
var config = {
emptyLevel: parseInt(msg.payload.empty_level) || 5,
lowLevel: parseInt(msg.payload.low_level) || 20,
fullLevel: parseInt(msg.payload.full_level) || 90,
lowMinInterval: (parseFloat(msg.payload.low_interval_hours) || 2) * 3600,
skipFull: msg.payload.skip_full === "true",
trendEnabled: msg.payload.trend_enabled === "true",
rapidDropThreshold: parseInt(msg.payload.rapid_drop) || 10
};
// 保存到 Flow Context
flow.set("dosingConditions", config);
node.log("Conditions updated: " + JSON.stringify(config));
return { payload: "配置已更新: " + JSON.stringify(config) };
┌──────────────────────────────────────────────┐
│ 条件控制配置 │
├──────────────────────────────────────────────┤
│ │
│ 🛡️ 安全保护: │
│ 缺水阈值: [5] % 禁止投料 ✅ │
│ │
│ ⚠️ 紧急投料: │
│ 低液位阈值: [20] % │
│ 最小间隔: [2] 小时 │
│ │
│ ✅ 满液位跳过: [90] % 跳过投料 ✅ │
│ │
│ 📊 趋势检测: │
│ 启用: [✅] │
│ 快速下降阈值: [10] %/次 │
│ │
│ [保存配置] │
│ │
│ --- 当前状态 --- │
│ 液位: 33% │
│ 投料调度: 跳过 (间隔未到) │
│ 条件覆盖: 否 │
│ 最终指令: 0 │
│ │
└──────────────────────────────────────────────┘
输入: {
scheduledCommand: "1", // 时间调度说需要投料
levelPercent: 95, // 但是容器几乎是满的
conditions: {
fullLevel: 90,
skipFull: true
}
}
条件检查:
1. 缺水? level=95 > emptyLevel=5 → 通过
2. 紧急投料? level=95 > lowLevel=20 → 不触发
3. 满液位跳过? level=95 > fullLevel=90 AND scheduled=1 → 触发!
输出:
command: "0" // 跳过本轮投料
reason: "full_container_skip"
overridden: true
Terminal window
# 1. 测试缺水保护
# 触发: 液位 < 5%
# 预期: 即使时间调度为 "1",也输出 "0"
# 日志: SAFETY: Container nearly empty (3%), dosing inhibited
# 2. 测试低液位紧急投料
# 触发: 液位 < 20% AND 距上次投料 > 2 小时
# 预期: 即使时间调度为 "0",也输出 "1"
# 日志: EMERGENCY: Low level (15%), forcing dosing
# 3. 测试满液位跳过
# 触发: 液位 > 90% AND 时间调度为 "1"
# 预期: 输出 "0"
# 日志: SKIP: Container full (95%), skipping scheduled dosing
# 4. 测试液位快速下降检测
# 触发: 液位从 50% 降到 35% (下降 15%)
# 预期: rapidDropAlert 触发告警

Q1: 条件覆盖优先级是如何确定的?

Section titled “Q1: 条件覆盖优先级是如何确定的?”

安全 > 紧急 > 跳过 > 时间调度。即安全保护(缺水禁止投料)优先级最高,低液位紧急投料次之,满液位跳过再次之,最后才是时间调度的正常结果。

按优先级处理:先检查安全条件 → 再检查紧急条件 → 再检查跳过条件 → 最后使用时间调度结果。每个条件分支使用 else if 确保只有一个条件生效。

Q3: 能否根据温度/湿度条件调整投料?

Section titled “Q3: 能否根据温度/湿度条件调整投料?”

可以。通过 MQTT 接入温湿度传感器,在条件逻辑中增加判断:

// 温度补偿: 高温增加投料量
var temperature = flow.get("currentTemperature") || 25;
if (temperature > 35 && shouldDose) {
dosingDuration = 5000; // 高温多投一些
}

推荐做法:

  • 安全保护条件(缺水)的优先级最高
  • 所有条件阈值可在 Dashboard 远程配置
  • 每次条件覆盖都记录原因到日志
  • 条件覆盖状态在 Dashboard 上实时显示
  • 条件参数设置合理范围(如液位 0-100%)

避免做法:

  • 相互冲突的条件同时启用(如缺水强制不投料 AND 低液位紧急投料)
  • 条件阈值设置过于敏感(如 emptyLevel = 30% 过早禁止投料)
  • 忽略条件覆盖后的审计日志
  • 条件配置存储在 Node-RED 内存中(重启丢失)
  1. 四层条件: 安全保护 → 紧急投料 → 满液位跳过 → 时间调度
  2. 液位触发: 低液位紧急投料和满液位跳过互不冲突
  3. 趋势检测: 快速液位下降提示泄漏风险
  4. 远程配置: 所有条件阈值通过 Dashboard 动态调整
  5. 决策透明: 每次条件覆盖记录原因和元数据