工业告警引擎:滞回、状态机与防抖设计
阈值告警是工业监控的基本功。但一个简单的”超出上限就报警”会在边界处疯狂抖动——警报响了又灭、灭了又响。本文介绍如何在数据管道中植入一个可靠的告警引擎。
问题:为什么不能在边界处反复报警?
假设温度上限 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 的反复穿越被滞回带消化。
优化方向
- 自适应阈值:当前阈值是静态配置。可以根据滑动窗口的历史数据自动计算 μ±3σ 作为动态阈值,适应传感器特性漂移
- 告警等级:目前只有”告警”/“正常”两级。可扩展为 Warning(接近阈值 90%)和 Critical(越限),不同等级不同颜色
- 告警联动:
alarmTriggered信号目前只是日志输出。T032 会做一个告警面板(红色闪烁 + 声音),信号已经就绪,接上就能用 - 上升沿 vs 电平:当前是电平触发(超过即报警,回落才清除)。有些场景需要上升沿触发(只报警一次,直到操作员确认)。可通过增加
acknowledge()方法实现
总结
告警引擎的质量不取决于阈值设得有多准,而取决于该报警时一定报,不该报时一定不报。滞回带就是这”一定”的数学保障。把告警放在数据管道末尾、过滤之后,则是工程上的兜底——垃圾数据不会变成垃圾告警。