条件控制逻辑
条件控制逻辑
本节介绍在投料系统中实现条件控制逻辑的方法——即在特定条件下覆盖默认的时间调度,实现更智能的投料控制。学习完成后,您将能够:
- 设计液位触发的条件投料逻辑
- 实现多条件组合决策
- 配置安全保护条件(缺水断电等)
- 通过 Node-RED Dashboard 动态调整条件参数
Conditional Logic Framework
Section titled “Conditional Logic Framework”┌──────────────────────────────────────────────────────────────┐│ 条件控制决策树 │├──────────────────────────────────────────────────────────────┤│ ││ 输入: ││ - 时间调度结果 (投料/不投料) ││ - 当前液位 (%) ││ - 上次投料时间 ││ - 液位变化趋势 ││ ││ 条件判断 (按优先级): ││ ││ 1. 安全条件: ││ 液位 < 5% → 禁止投料 (缺水,泵会空转) ││ ││ 2. 低液位紧急投料: ││ 液位 < 20% AND 超过最小间隔 → 强制投料 ││ ││ 3. 满液位跳过投料: ││ 液位 > 90% → 跳过本次计划投料 ││ ││ 4. 时间调度结果: ││ 正常投料/不投料 (默认) ││ ││ 输出: ││ - 最终指令 (投料/不投料) ││ - 决策原因 (供日志和 Dashboard 显示) ││ │└──────────────────────────────────────────────────────────────┘Node-RED Implementation
Section titled “Node-RED Implementation”// 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;Dashboard: Condition Configuration UI
Section titled “Dashboard: Condition Configuration UI”// 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 Contextflow.set("dosingConditions", config);
node.log("Conditions updated: " + JSON.stringify(config));
return { payload: "配置已更新: " + JSON.stringify(config) };┌──────────────────────────────────────────────┐│ 条件控制配置 │├──────────────────────────────────────────────┤│ ││ 🛡️ 安全保护: ││ 缺水阈值: [5] % 禁止投料 ✅ ││ ││ ⚠️ 紧急投料: ││ 低液位阈值: [20] % ││ 最小间隔: [2] 小时 ││ ││ ✅ 满液位跳过: [90] % 跳过投料 ✅ ││ ││ 📊 趋势检测: ││ 启用: [✅] ││ 快速下降阈值: [10] %/次 ││ ││ [保存配置] ││ ││ --- 当前状态 --- ││ 液位: 33% ││ 投料调度: 跳过 (间隔未到) ││ 条件覆盖: 否 ││ 最终指令: 0 ││ │└──────────────────────────────────────────────┘Complete Decision Flow
Section titled “Complete Decision Flow”输入: { 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# 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 触发告警Common Customer Questions
Section titled “Common Customer Questions”Q1: 条件覆盖优先级是如何确定的?
Section titled “Q1: 条件覆盖优先级是如何确定的?”安全 > 紧急 > 跳过 > 时间调度。即安全保护(缺水禁止投料)优先级最高,低液位紧急投料次之,满液位跳过再次之,最后才是时间调度的正常结果。
Q2: 多个条件同时触发怎么办?
Section titled “Q2: 多个条件同时触发怎么办?”按优先级处理:先检查安全条件 → 再检查紧急条件 → 再检查跳过条件 → 最后使用时间调度结果。每个条件分支使用 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 内存中(重启丢失)
Summary
Section titled “Summary”- 四层条件: 安全保护 → 紧急投料 → 满液位跳过 → 时间调度
- 液位触发: 低液位紧急投料和满液位跳过互不冲突
- 趋势检测: 快速液位下降提示泄漏风险
- 远程配置: 所有条件阈值通过 Dashboard 动态调整
- 决策透明: 每次条件覆盖记录原因和元数据