4. 报警消息的实时生成与推送

报警从“被检测到”到“被人看到”,中间要经过一条完整的消息链路:报警源产生事件、系统组装成标准消息、进入队列、按订阅关系分发、经协议映射落到具体通道,最后由 HMI、短信、邮件或声光设备呈现。这条链路上任何一环设计不当,都会造成漏报、重复报或延迟超标。本节围绕消息结构、队列与发布订阅、协议映射、推送通道和延迟控制五个方面展开。

一、报警消息的标准化结构

报警消息必须结构化,否则下游无法路由、无法去重、无法统计。一条典型的报警消息至少包含六个字段:消息ID、发生时间、设备标识、报警代码、描述文本、报警等级。工程上还会附加状态(激活/确认/清除)、来源协议、区域、建议处置等扩展字段。

其中时间字段最容易出问题。建议统一使用带时区的 UTC 时间戳(如 ISO 8601 格式 2025-03-11T08:15:32.480Z),由采集端打时间戳,而不是由推送端补时间,否则队列积压时时间会整体偏移。消息ID建议采用“设备ID+代码+发生时间”的确定性组合或全局递增序列,前者便于去重,后者便于排序。报警等级可按四级划分:紧急(需立即停机处置)、重要(影响质量或节拍)、一般(需关注)、提示(仅记录)。等级直接决定推送通道和延迟预算,是后续路由的核心依据。

{
  "msgId": "L3-PRESS-0217-20250311T081532480Z",
  "ts": "2025-03-11T08:15:32.480Z",
  "device": "PR-03 液压冲压机",
  "code": "E0217",
  "desc": "液压油温超上限",
  "level": "重要",
  "state": "激活",
  "source": "OPC-UA"
}

二、消息队列与发布订阅

报警产生速率具有明显的突发性:平时每分钟几条,故障连锁时可能每秒上百条。若采集端直接调用推送接口,接口阻塞会反压到采集线程,导致更多报警丢失。因此中间必须引入消息队列做削峰和解耦。常用选择包括 MQTT(轻量、适合边缘)、Kafka(高吞吐、可回溯)、RabbitMQ(路由灵活)。

队列之上采用发布订阅模型:报警服务作为发布者,把消息发布到按主题划分的通道,例如 alarm/line1/press/important;HMI 网关、短信服务、邮件服务、声光控制器各自作为订阅者,按主题通配符订阅自己关心的消息。这样新增一个推送通道时,只需增加订阅者,不必改动报警源,扩展性最好。订阅端还应实现幂等消费:以 msgId 为键做短期去重缓存,防止网络重投造成重复弹窗或重复短信。

三、OPC UA 与 Modbus 的报警映射

不同协议带来的报警语义不同,映射时要保留原始语义。OPC UA 本身有成熟的报警与条件模型(AlarmCondition),事件通过订阅(Subscription)以 MonitoredItem 方式上报,带有 EventId、Severity、SourceName、Time 等字段,可直接映射到标准消息的 ID、等级、设备和时间,Severity 通常为 1~1000,可按阈值折算为四级。

Modbus 没有报警语义,只有寄存器。工程上通常约定:某保持寄存器或线圈的某一位为 1 表示报警激活,配合一张“寄存器地址—报警代码—描述—等级”的映射表完成翻译。例如 40001 的第 3 位对应 E0217 油温超限。由于 Modbus 是轮询式,采集周期直接决定报警发现延迟,周期设为 200 ms 时,最坏发现延迟约 200 ms,这是延迟预算中不可忽略的一项。映射表应作为配置资产统一维护,避免代码里硬编码地址。

OPC UA 设备 AlarmCondition 事件 Modbus 设备 寄存器位轮询 200ms 协议映射层 Severity→等级 地址位→代码/描述 统一 UTC 时间戳 消息组装 ID/时间/设备 代码/描述/等级 状态/来源 消息队列 削峰 / 解耦 MQTT / Kafka 主题分区 发布订阅分发 alarm/line1/... 按等级路由 msgId 幂等去重 HMI 弹窗 ≤1s 短信 ≤10s 邮件 ≤60s 声光报警器 ≤500ms 橙色为分发路径:同一报警按等级与订阅关系并行推送至多个通道,各通道延迟预算独立考核

四、推送通道与实时性要求

不同通道的定位不同,延迟预算也不同。HMI 弹窗是操作工的第一感知手段,要求从报警发生到画面刷新不超过 1 秒;声光报警器用于现场强提醒,要求 500 毫秒内动作;短信面向班组长和值班工程师,10 秒内送达即可;邮件用于归档和日报,60 秒内可接受。紧急等级应同时触发 HMI、声光和短信,重要等级触发 HMI 与声光,一般和提示等级只进 HMI 与事件库。

延迟控制要从链路上逐段核算。以 Modbus 油温报警为例:轮询周期 200 ms(最坏发现延迟 200 ms)+ 映射与组装约 10 ms + 队列排队(正常小于 50 ms,突发时可能到数百毫秒)+ 分发与推送 100 ms,合计正常约 360 ms,满足 HMI 1 秒预算。若队列积压导致排队超过 500 ms,就应触发队列深度告警,并考虑提高消费者并发或对低等级消息降级批量处理。此外,短信网关和邮件服务器属于外部依赖,必须设置超时与失败重试上限,避免一个慢通道拖垮整个分发线程;实践中常把外部通道放入独立线程池或独立消费者组,与 HMI 推送隔离。

延迟预算应按“最坏情况”而非平均值核算,并把轮询周期、队列排队、外部网关响应三项单独列出,便于定位瓶颈。

五、一个可执行的推送策略配置示例

把上述规则固化为配置,是保证一致性的有效做法。下面是一份按等级路由的策略片段,可作为实际配置的参考骨架:

routes:
  - level: 紧急
    channels: [hmi, sound_light, sms]
    max_delay_ms: 500
    repeat_until_ack: true
  - level: 重要
    channels: [hmi, sound_light]
    max_delay_ms: 1000
  - level: 一般
    channels: [hmi]
    max_delay_ms: 2000
  - level: 提示
    channels: [hmi, event_log]
    max_delay_ms: 5000
dedup:
  key: msgId
  window_s: 30
queue:
  depth_alert: 500
  consumer_concurrency: 4

上线后应做一次端到端验证:人为短接或模拟一个 Modbus 报警位,用抓包或日志时间戳比对“报警置位时刻”与“HMI 弹窗时刻”,确认差值落在预算内;再连续触发 200 条报警,观察队列深度是否超过告警阈值、是否有消息因去重窗口设置不当被误吞。只有这条链路在正常与突发两种工况下都可观测、可判断,报警推送才算真正落地。