标签

#SpringBoot #工控告警 #防抖滤波 #SCADA #工业物联网

摘要

工业设备模拟量数值普遍存在瞬时抖动、瞬间跳变问题,直接开启越限告警会产生大量误报。本文基于上一篇基础告警引擎,新增延时防抖、持续校验、数据滤波机制,彻底解决工控、地铁 ISCS 项目高频误告警痛点,代码无损兼容原有告警逻辑,生产直接替换上线。

一、前言

上一篇我们实现了工控基础告警:越限告警 + 突变告警。
但是!裸奔的告警逻辑在现场根本无法直接用

真实工控、轨交现场通病:

  1. 传感器信号干扰,数值瞬间跳一下马上恢复
  2. 485/网口采集偶尔出现瞬时脏数据
  3. 设备启停瞬间数值抖动

如果不做防抖,系统会一秒几十条误告警,运维直接崩溃、项目验收不通过。

所以本篇必须补上:工业级告警防抖 + 持续校验机制
只有「持续异常才算告警,瞬时抖动直接过滤」。

二、工业防抖核心原理(现场标准)

  1. 不触发瞬时告警
    数值第一次超限不立即告警
  2. 延时持续校验
    必须连续 N 次采集 / 持续 N 秒异常
  3. 稳定异常才触发告警
  4. 恢复也需要防抖:数值回落正常后,持续稳定再消除告警

完美适配地铁 BAS、电力监控、工厂 SCADA 标准逻辑。

三、新增告警规则字段(支持防抖配置)

在原有 AlarmRule 新增防抖参数,兼容上篇所有代码

import lombok.Data;

@Data
public class AlarmRule {

    // 点位 ID
    private String pointId;
    // 设备 ID
    private String deviceId;
    // 告警类型 1 越限 2 突变
    private Integer alarmType;

    // 上下限
    private Double maxValue;
    private Double minValue;
    private Double changeThreshold;

    // ========== 本篇新增防抖参数 ==========
    // 防抖持续次数:连续多少次异常才告警
    private int filterCount = 3;
    // 恢复防抖次数
    private int recoverFilterCount = 3;

    // 点位信息
    private String pointName;
    private String systemType;
}

四、点位防抖缓存实体(记录连续异常次数)

import lombok.Data;

@Data
public class AlarmCacheInfo {
    // 连续异常次数
    private int errorCount;
    // 连续正常次数
    private int normalCount;
    // 当前是否处于告警状态
    private boolean isAlarmStatus;
}

五、升级后【带防抖完整告警引擎】(核心源码)

import org.springframework.stereotype.Component;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

@Component
public class AlarmFilterEngine {

    // 点位防抖缓存 key:pointId
    private final Map<String, AlarmCacheInfo> alarmCacheMap = new ConcurrentHashMap<>();

    /**
     * 带防抖的告警判断
     * @return true=真正触发告警 / false=抖动或正常
     */
    public boolean checkAlarmWithFilter(CollectDataDTO data, AlarmRule rule) {
        String pointId = data.getPointId();
        double currVal = data.getValue();

        // 获取该点位缓存,没有则初始化
        AlarmCacheInfo cache = alarmCacheMap.getOrDefault(pointId, new AlarmCacheInfo());

        // 1、判断当前数据是否异常
        boolean isError = isOverLimit(currVal, rule) || isValueJump(pointId, currVal, rule);

        if (isError) {
            // 数据异常:异常次数累加、正常次数清零
            cache.setErrorCount(cache.getErrorCount() + 1);
            cache.setNormalCount(0);

            // 未处于告警状态,且连续异常达到防抖阈值 → 正式触发告警
            if (!cache.isAlarmStatus() && cache.getErrorCount() >= rule.getFilterCount()) {
                cache.setAlarmStatus(true);
                alarmCacheMap.put(pointId, cache);
                return true;
            }

        } else {
            // 数据正常:正常次数累加、异常次数清零
            cache.setNormalCount(cache.getNormalCount() + 1);
            cache.setErrorCount(0);

            // 当前处于告警状态,且持续正常达到恢复阈值 → 解除告警
            if (cache.isAlarmStatus() && cache.getNormalCount() >= rule.getRecoverFilterCount()) {
                cache.setAlarmStatus(false);
            }
        }

        alarmCacheMap.put(pointId, cache);
        return false;
    }

    // 越限判断
    private boolean isOverLimit(double val, AlarmRule rule) {
        return val > rule.getMaxValue() || val < rule.getMinValue();
    }

    // 突变判断
    private boolean isValueJump(String pointId, double curr, AlarmRule rule) {
        Double lastVal = LastValueCache.get(pointId);
        if (lastVal == null) return false;
        return Math.abs(curr - lastVal) > rule.getChangeThreshold();
    }
}

六、全局历史值缓存工具类(适配突变防抖)

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class LastValueCache {
    private static final Map<String, Double> LAST_MAP = new ConcurrentHashMap<>();

    public static void put(String pointId, Double val) {
        LAST_MAP.put(pointId, val);
    }

    public static Double get(String pointId) {
        return LAST_MAP.get(pointId);
    }
}

七、业务调用方式(完全兼容旧代码)

// 获取实时数据
CollectDataDTO data = collectService.getRealTimeData();
// 获取告警规则
AlarmRule rule = ruleService.getRuleByPointId(data.getPointId());

// 防抖校验
boolean realAlarm = alarmFilterEngine.checkAlarmWithFilter(data, rule);

if (realAlarm) {
    // 正式告警:入库 + 推送大屏
    tdEngineService.saveAlarm(data, rule);
    websocketService.pushAlarm(data, rule);
}

// 更新历史值,用于下一次突变判断
LastValueCache.put(data.getPointId(), data.getValue());

八、工业 / 轨交现场标准配置参数

  • 普通模拟量(温度 / 压力 / 液位)
    防抖次数:3 次
  • 波动大的电流、功率点位
    防抖次数:5~8 次
  • 开关量、状态量
    可关闭防抖或 2 次即可

九、现场落地优势(8 年轨交经验总结)

  1. 彻底消灭瞬时误告警
    95% 以上的信号抖动、瞬时脏数据全部过滤
  2. 不丢失真实故障
    真实持续异常一定会触发告警
  3. 告警恢复更平稳
    设备恢复稳定后才消除告警,避免反复闪断
  4. 完全兼容上一版代码
    无缝升级,无需改动原有存储、推送逻辑

十、总结

本篇在基础告警之上,补齐工业防抖、延时校验核心能力,
至此你的告警系统达到商用 SCADA、地铁 ISCS 验收标准。
不再乱告警、不再刷屏、运维体验拉满。

持续连载更新
本专栏基于 8 年轨道交通工控落地经验,从零手写一套完整工业物联网 SCADA 架构。
需要全套可运行源码的朋友评论区扣【源码】,关注持续更新!

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐