输入关键词开始搜索

工业告警引擎:滞回、状态机与防抖设计

阈值告警是工业监控的基本功。但一个简单的”超出上限就报警”会在边界处疯狂抖动——警报响了又灭、灭了又响。本文介绍如何在数据管道中植入一个可靠的告警引擎。


问题:为什么不能在边界处反复报警?

假设温度上限 100°C。传感器在 99.5°C 和 100.2°C 之间高频波动:

99.5 → 正常
100.2 → 告警!← 触发
99.8 → 清除
100.1 → 告警!← 又触发
99.7 → 清除
...

每秒可能触发几十次。操作员的耳朵和眼睛会在几分钟内免疫,真正的危险来临时反而视而不见——狼来了效应

物理世界的噪声决定了阈值检测必须设计滞回带


设计:状态机 + 滞回

          ┌──── 上限 ─────────────────────
          │  105 → 报警!(穿越上限)
          │  103 → 保持报警
          │   98 → 保持报警(还在滞回带里)
          │   94 → 解除报警(成功穿越滞回带)
          └──── 上限 - 滞回 ────────────────
          
              正常区间,无报警
              
          ┌──── 下限 + 滞回 ────────────────
          │   56 → 解除报警
          │   52 → 保持报警
          │   48 → 保持报警(还在滞回带里)
          │   45 → 报警!(穿越下限)
          └──── 下限 ─────────────────────

状态转换

                 值 >= 上限
     Normal ────────────────→ AboveUpper
       ↑                          │
       │  值 < 上限 - 滞回         │
       └──────────────────────────┘

                 值 <= 下限
     Normal ────────────────→ BelowLower
       ↑                          │
       │  值 > 下限 + 滞回         │
       └──────────────────────────┘

关键规则:只在状态变化的瞬间发射一次信号

if (cfg.upperEnabled && val >= cfg.upper) {
    if (state != AboveUpper) {          // ← 状态不同才发射
        state = AboveUpper;
        emit alarmTriggered(ch, val, cfg.upper, true);
    }
}

val 保持 ≥ 上限的整个区间内,只触发一次 alarmTriggered。同样,alarmCleared 只在穿越滞回带时发射一次。这从根本上杜绝了抖动。


实现:为什么走 IFilter 管道?

ThresholdAlarm 同时继承 QObject(发射信号)和 IFilter(数据透传):

FilterPipeline

    ├── MA(8)        ← 先平滑
    ├── Median(5)    ← 再去尖刺
    └── ThresholdAlarm ← 最后检测(此时数据已干净)

这个顺序很重要。如果把告警放在平滑之前,噪声 + 脉冲野值会造成大量误报。放在管道末尾,检测的是已经过滤后的”可信数据”。

process() 方法纯透传——数据原样返回,不篡改下游的缓冲和数据库:

DataPoint ThresholdAlarm::process(const DataPoint &dp) {
    // 检测逻辑...
    return dp;  // 原样返回
}

管道中的其他过滤器(MA、Median)修改数据,告警只”观察”——职责清晰。


滞回:全局 vs 每通道?

方案优点缺点
全局简单,一个参数不够灵活
per-channel精确适配不同传感器N 个参数,配置负担重

选择全局。

滞回带本质上是对”信号自然波动范围”的估计。同一台设备的 4 个热电偶通道,温度波动量级是相同的(±1°C)。如果通道 A 波动 ±5°C 而通道 B 波动 ±0.1°C,说明通道 A 有问题——这时候不应该靠调大滞回来压制,而应该去修传感器。

工业 DCS 系统(如西门子 PCS7、横河 CENTUM)普遍使用全局滞回——复杂度的增加需要实际场景支撑。


通道掩码:不是所有通道都需要告警

温度传感器需要上限告警,但状态寄存器(0=正常, 1=故障)不需要。通过 setChannels([0,1]) 可以精确控制:

for (int ch = 0; ch < dp.channels.size(); ++ch) {
    if (!channelActive(ch)) continue;  // 未选中的跳过
    // 检测逻辑
}

未选中的通道保持原值透传,下游不受影响。


日志验证

连接噪声模拟器,添加 MA(8) + Median(5) + ThresholdAlarm(上限=900, 下限=100, 滞回=5):

00:22:43  ThresholdAlarm: CH0 超上限 903.6 >= 900     ← 正弦波峰
00:22:44  ThresholdAlarm: CH0 上限告警清除              ← 回落到 895 以下
00:22:45  ThresholdAlarm: CH0 超上限 903.6 >= 900     ← 下一周期
00:22:46  ThresholdAlarm: CH0 上限告警清除

...持续 8 秒,每 2 秒一个周期,无重复触发...          ← 滞回防抖生效

每个正弦周期(2s)只触发一次 alarmTriggered 和一次 alarmCleared,边界 899↔901 的反复穿越被滞回带消化。


优化方向

  1. 自适应阈值:当前阈值是静态配置。可以根据滑动窗口的历史数据自动计算 μ±3σ 作为动态阈值,适应传感器特性漂移
  2. 告警等级:目前只有”告警”/“正常”两级。可扩展为 Warning(接近阈值 90%)和 Critical(越限),不同等级不同颜色
  3. 告警联动alarmTriggered 信号目前只是日志输出。T032 会做一个告警面板(红色闪烁 + 声音),信号已经就绪,接上就能用
  4. 上升沿 vs 电平:当前是电平触发(超过即报警,回落才清除)。有些场景需要上升沿触发(只报警一次,直到操作员确认)。可通过增加 acknowledge() 方法实现

总结

告警引擎的质量不取决于阈值设得有多准,而取决于该报警时一定报,不该报时一定不报。滞回带就是这”一定”的数学保障。把告警放在数据管道末尾、过滤之后,则是工程上的兜底——垃圾数据不会变成垃圾告警。