边缘计算与协议 · 2026.08.22

工业 IoT 的告警和事件为什么不能混着建模:从事实记录到可审计处置

工业告警不是带 severity 的事件。本文用状态机与一手测试说明如何分开事件事实、告警 episode、确认处置、抑制、审计和通知,避免告警风暴与历史失真。

工业 IoT 的告警和事件为什么不能混着建模:从事实记录到可审计处置

一台冷水机的出水温度在十分钟内六次越过高限,又五次回到正常范围。很多平台会生成十一条“告警”,每条都有时间、级别和一段文案。值班员确认第一条后,剩余十条仍然待处理;如果系统为了减少噪声只保留最后一条,最早何时越界、波动了几次、哪个规则版本做出判断又会丢失。两种结果看起来相反,根因却相同:平台把发生过的事件、持续存在的异常条件、需要人员响应的告警,以及发给某个渠道的通知塞进了同一张表。

本文的结论是:事件是不可变的发生事实,告警是由一个或多个事实驱动、需要人员或流程响应的受控 episode;确认、恢复、关闭、搁置和抑制属于告警工作流,短信、邮件和工单只是告警的投递或处置结果。 当这几个对象被拆开后,平台才能同时做到不删除原始证据、不过量打扰值班员,并回答“发生了什么、当前是否仍异常、谁已经知道、为什么没有展示、采取了什么动作”。

这不是把某个项目的字段命名包装成行业标准。Grus 当前代码把告警处置限制为 open → acknowledged → resolved → closed,为每次迁移记录操作人、时间、前一状态、评论、设备和 trace_id,并把 alarm.openedalarm.resolved 作为持久平台事件发布。本轮重新执行六项聚焦测试,结果为 6 passed。这些证据能证明特定实现中的状态保护、审计、租户/责任人隔离和事件发布,不证明现场告警性能、认证符合性、告警率目标或所有行业都应采用同一状态名。

1. 一个阈值越界,不应直接等于一条告警

工业数据链里至少存在四种语义。观测是某个测点在某个 observed_at 的值和质量;事件是不可变的离散事实,例如“温度从正常进入高限”“泵从运行变为停止”“通讯在 10:03 中断”;告警是平台判定该异常需要指定角色及时响应后创建的可管理对象;通知是把告警或其他信息送到 HMI、短信、邮件、Webhook 或工单系统的一次交付尝试。四者可以相关,但生命周期完全不同。

事件一旦接纳就不应被“确认”或“关闭”。操作员不能让 high_limit_entered 这件事变成未发生,只能确认自己看到了由它触发的告警。通知也不等于告警:短信发送失败不意味着异常不存在,短信发送成功也不代表有人已经理解并接手。把通知状态写回告警状态,会让渠道故障改写生产事实;把确认写回事件,会破坏审计和重放。

并非每个状态变化都值得成为告警。设备进入维护模式、批次切换、配方加载或阀门按计划关闭,都是有价值的事件,却未必要求立即响应。IEC 62682:2022 把告警系统的首要功能描述为通知操作员异常过程条件或设备故障并支持响应,同时把 alarm/event log、historian 与性能指标列为相关能力。这给出了一条实用边界:只有在对象、异常条件、潜在后果、责任角色和期望响应都明确时,事件才有资格升级为告警。 仅有 severity: high 或颜色为红,不足以证明它可操作。

如果平台用同一张 alarm_event 表同时承担四种对象,常见后果会很快出现:一条记录被不断覆盖,无法重放;每次抖动都创建新记录,值班员被重复打扰;渠道重试制造重复“告警”;恢复事件自动删除待确认项;维护期过滤让原始事件一起消失。正确的修复不是再加一个 type 字段,而是先把对象边界与不可变性写清。

2. 用三类记录保留事实、关联和处置

最小实现不需要一开始就复制完整的过程工业标准模型,但至少需要三类持久记录。第一类是 event_occurrence,保存来源、设备、测点、发生时间、接入时间、事件 ID、类型、值、质量、规则或模型版本和原始 lineage。它只追加,不承担人员状态。第二类是 alarm_episode,保存为什么需要响应、当前是否 active、严重级别、责任范围、首发/末发时间、去重或关联键、触发规则版本和工作流状态。第三类是 alarm_action,只追加确认、搁置、取消搁置、分派、恢复、关闭、评论、工单和通知结果。

一个可执行的最小 schema 可以从下面的边界开始:

对象 关键字段 可变性 主要问题
event_occurrence event_id, source_ref, event_type, occurred_at, received_at, quality, payload_hash, rule_version append-only 发生了什么,何时发生,由谁观察
alarm_episode alarm_id, correlation_key, condition_state, workflow_state, severity, owner_scope, first_event_id, last_event_id 受状态机控制 当前是否需要响应,谁负责,处于哪一步
alarm_action action_id, alarm_id, action, actor, acted_at, reason, previous_state, trace_id append-only 谁做了什么,为什么,是否合规

表格之后的关键判断是:alarm_episode 不是事件的替代存储,而是事件之上的处置投影。事件和 action 都保留不可变证据,episode 只保存便于当前操作的聚合状态。即使 episode 被错误关闭,仍能通过事件与 action 重建;如果把所有历史压进一条可变告警行,任何误操作都会成为不可恢复的数据删除。

还要把 condition_stateworkflow_state 分开。设备温度可能已经恢复正常,但操作员仍未确认;也可能条件仍然 active,但值班员已经确认并开始处理。用一个 status 同时表达两条轴,会产生“确认后告警消失”或“恢复后自动等于已处理”的假象。最小模型可分别保存 active/inactiveunacknowledged/acknowledged/closed,再按行业需要扩展 confirmedshelvedsuppressedout_of_service 等正交状态。

3. 告警是一个 episode,不是一条可反复覆盖的消息

所谓 episode,是指从异常首次满足告警条件,到条件恢复并完成必要处置之间的一段关联过程。它可以包含多次原始采样、进入/退出事件、规则重评估、通知尝试和操作员动作。episode 的价值在于给人员一个稳定工作对象:同一次冷水机高温抖动不应生成十一张互不相干的待办,也不能因为只显示一张卡片就丢掉十一条事实。

创建 episode 时,需要先定义可解释的 correlation_key。常见组成包括 tenant_id + asset_id + alarm_definition_id + operating_mode,而不是简单使用“同设备五分钟内都合并”。时间窗口只能限制相关范围,不能代替语义。如果同一设备同时发生润滑压力低和电机绕组温度高,把它们合并成“设备异常”会让责任和响应动作变得模糊;如果同一根温度探头在规则版本切换后使用了不同阈值,也应保留版本边界,避免新规则解释旧事件。

一个 episode 的核心迁移可以保持克制:新条件触发时创建 open;责任人确认后进入 acknowledged;条件恢复且修复动作满足时进入 resolved;证据、评论或工单闭环后进入 closed。Grus 的当前实现使用这条严格迁移链,对跳过确认直接 open → resolved 返回 ALARM_STATE_NOT_ALLOWED,并在确认、恢复、关闭时分别保留时间戳和审计动作。该设计不是唯一标准,但验证了一个重要不变量:工作流迁移必须由明确规则保护,不能让任意 PATCH 把告警改成任何状态。

flowchart LR

E("事件事实<br/>event occurrence"):::blue --> O("创建告警 episode<br/>open + active"):::orange
O -->|操作员确认| A("已确认<br/>acknowledged + active"):::violet
A -->|条件恢复| R("已恢复<br/>resolved + inactive"):::green
R -->|证据闭环| C("已关闭<br/>closed + inactive"):::slate
O -->|条件先恢复| U("恢复但未确认<br/>open + inactive"):::cyan
U -->|补充确认与复核| R
O -.->|维护规则| S("shelved / suppressed<br/>仍保留状态迁移"):::red
A -.->|维护规则| S
S -.->|解除隐藏| O
S -.->|解除隐藏| A
O --> H("只追加 action / audit"):::blue
A --> H
R --> H
C --> H

classDef blue fill:#EAF4FF,stroke:#3B82F6,color:#16324F,stroke-width:2px;
classDef cyan fill:#E9FBF8,stroke:#14B8A6,color:#134E4A,stroke-width:2px;
classDef orange fill:#FFF3E8,stroke:#F08A24,color:#7C3F00,stroke-width:2px;
classDef violet fill:#F4EDFF,stroke:#8B5CF6,color:#4C1D95,stroke-width:2px;
classDef green fill:#ECFDF3,stroke:#22C55E,color:#14532D,stroke-width:2px;
classDef slate fill:#F8FAFC,stroke:#64748B,color:#1F2937,stroke-width:2px;
classDef red fill:#FFF1F2,stroke:#E11D48,color:#881337,stroke-width:2px;

图中的“恢复但未确认”很重要。OPC UA Part 9 把 alarm 建模为带 Active、Acknowledge、Shelving 和 Suppressed 等状态的 Condition;AckedState 可以在条件已经变化后仍要求客户端追踪先前状态。实现可以比规范简单,但不能假定 condition inactive 就自动等于 operator acknowledged。否则无人响应的短时异常会在恢复后从界面和责任统计中消失。

4. 去重、关联与抑制:减少噪声不能删除事实

告警风暴通常不是靠更大的数据库解决,而是靠三层不同机制收敛。第一层是事件去重:来源重试或网络重放带来相同 event_id + payload_hash 时,只接纳一次;相同 event_id 携带不同内容时必须拒绝并生成碰撞 reason code,不能静默覆盖。第二层是episode 关联:同一告警定义、同一资产和同一运行上下文的重复进入事件,追加到当前 episode,而不是创建新待办。第三层是通知抑制:在维护、停机、风暴或责任交接场景中减少当前展示和渠道投递,但保留底层事件与 episode 状态。

这三层不能合并成一个“十分钟内不再告警”的 debounce。Debounce 适合过滤传感器抖动,但它会推迟首次响应;去重处理重复事实,但不能消灭真正的多次变化;关联降低待办数量,却仍要保留事件计数、首发和末发时间;抑制改变展示策略,不应停止 condition 状态迁移。OPC UA Part 9 明确说明 suppressed、out of service 或 shelved 时,状态迁移仍会发生,只是客户端通常不显示。这条原则能避免维护窗口结束后系统错误地认为期间从未发生异常。

值班交接时核对告警记录、设备面板与通讯工具

抑制动作本身必须可追溯。系统需要记录谁、基于什么维护工单、在什么范围、从何时到何时、针对哪个告警定义或资产执行;超时后自动解除,解除失败要形成单独的运维事件。把“静音”“确认”“搁置”“抑制”和“停用”做成同一个按钮,会让统计失真:静音只影响声音,确认代表责任人已看到,搁置是操作员临时隐藏,抑制通常来自系统上下文,停用则意味着告警定义不参与判断。它们的权限、时效和审计要求不同。

根因关联也不应直接关闭下游告警。当一条上游供电故障触发二十台设备离线时,可以把二十条 episode 归到同一个 incident 或 causal group,并优先显示根因;下游事件仍要保存,因为它们证明影响范围。只有在恢复条件和处置策略明确时,incident 的关闭才能推动下游 episode 复核,不能用一个“已关联”字段批量抹平状态。

5. 用时间、版本与审计处理迟到、重放和规则变更

工业事件至少需要区分 occurred_atreceived_atprocessed_atoccurred_at 来自设备或边缘侧,说明现场何时发生;received_at 说明平台何时接到;processed_at 说明规则或投影何时完成。设备离线后补传时,三者可能相差数小时。如果只用数据库创建时间,值班员会看到一条“刚刚发生”的旧故障;如果只相信设备时间,又可能被错误时钟破坏排序。平台应保存三者和时钟质量,再明确哪一个用于告警触发、SLA 和审计。

迟到事件不应自动改写已关闭 episode。更稳妥的做法是先按 event_id 接纳事实,再根据 episode 的时间范围、规则版本和关闭策略决定:追加为 late evidence、重新打开、创建补充 episode,或进入人工复核。决策要生成 reason code。没有 reason code 的“忽略迟到数据”无法区分重复、过期、越权、版本不兼容还是代码缺陷。

规则版本同样必须进入 lineage。一个告警至少记录 alarm_definition_idrule_version、阈值、deadband/on-delay/off-delay 参数的制品引用,以及激活时间。修改阈值时,新事件由新版本解释;既有 episode 默认继续保留创建时的定义,除非迁移计划明确要求重评估。直接就地修改规则并重新计算全部 active alarms,可能同时改变严重级别、责任范围和关闭条件,造成值班员看到的工作对象无解释地跳变。

审计记录要覆盖“系统为什么做”和“人为什么做”两条线。系统线保存来源事件、规则执行、关联选择、抑制原因、通知结果和状态投影;人员线保存确认人、评论、分派、工单、关闭依据和强制覆盖。Grus 的实现把每次迁移写入目标为 alarm 的审计记录,包含 previous_status、新状态、操作人、评论、设备、severity 与 trace_id,并允许按 target、trace 和 device 查询。本轮测试还验证了 vendor 只能操作其 service owner 范围内的告警,说明工作流权限必须和资产责任边界一起实现,不能只在前端隐藏按钮。

6. 从最小实现到生产验收:先演练六种失败

第一阶段只需要可证明的骨架:append-only 事件表、受控 alarm episode、append-only action/audit、稳定 correlation key、事件/条件/工作流双轴状态、规则版本和基础通知记录。不要一开始实现复杂根因图谱、机器学习优先级或自动工单编排;如果事件事实和状态机还不可靠,高级相关只会更快地产生不可解释结果。

上线前至少演练六种失败。其一,重复投递同一个 event,确认 episode 只关联一次;其二,相同 event ID 携带不同 payload,确认系统 fail closed 并保留碰撞证据;其三,条件在确认前恢复,确认 episode 仍能被补充确认和复核;其四,非法跳过状态,例如 open → resolved,确认 API 拒绝且原状态不变;其五,维护抑制期间条件多次变化,确认事件与 active/inactive 轨迹仍然存在;其六,旧规则版本的迟到事件到达,确认不会无理由重开或改写已关闭告警。

每次演练都要同时检查四个结果:当前 episode 状态是否正确,事件历史是否完整,action/audit 是否能解释迁移,通知是否具有独立状态。只检查 HMI 上“红灯消失”不足以验收,因为 UI 可能把数据过滤掉;只检查数据库新增一行也不足以验收,因为责任人可能收不到或无法确认。最小验收矩阵应覆盖 API、存储、审计、权限和渠道五个边界。

回滚也要分层。规则版本出错时回滚定义和激活指针,不能删除已生成事件;关联算法出错时重建 episode projection,并保留旧、新关联结果和迁移审计;通知模板或渠道出错时重试投递,不改告警状态;状态机代码出错时先冻结危险迁移,再从 event/action 重算当前投影。能够从不可变事实重建,是告警模型最重要的回滚能力。 如果恢复依赖人工猜测某行在覆盖前是什么,模型就没有真正的审计基础。

运行指标也要按层拆开。事件层看接纳、重复、碰撞、迟到和 quarantine;episode 层看 standing alarm、chattering、平均 active 时长和重复关联;工作流层看确认延迟、未关闭、强制覆盖和越权拒绝;通知层看投递延迟、失败、重试和渠道降级。把所有数字压成“告警总数”无法判断问题来自现场、规则、人员还是渠道。

7. 什么时候简单事件表够用,什么时候必须升级模型

如果系统只做无人值守数据记录,事件不要求人员响应,没有确认、分派、抑制、工单、合规审计或责任 SLA,那么 append-only 事件表加查询视图就够用。小型设备的本地保护逻辑也可能只需要状态变化记录,因为真正的安全响应已经由硬接线或控制器完成。此时强行引入完整告警生命周期,会增加同步、权限和 UI 成本,却没有对应收益。

当异常需要人在限定时间内理解和行动,或者同一事件要跨 HMI、短信、工单、班组和审计流转时,就必须把告警建成独立 episode。多租户、多个 service owner、维护窗口、迟到补传、规则版本、根因关联和合规留痕,会进一步要求独立 action log 与权限边界。判断点不是设备数量,而是异常是否形成了可追责的响应义务。

即使采用 ISA-18.2、IEC 62682 或 OPC UA Alarms & Conditions 的术语,也不要直接宣称标准符合性。标准提供生命周期、状态和职责的参考;具体 alarm philosophy、优先级、允许响应时间、HMI、性能指标和审计要求仍需由工艺风险、现场组织与合规要求决定。本文没有验证告警洪泛容量、操作员负荷、长期可用性、安全完整性或认证,因此这些结论必须在目标现场重新测量和评审。

结论

工业平台减少“告警太多”的第一步,不是增加静音按钮,而是停止把每个事件都当告警、每次通知都当处置。事件要不可变地回答发生了什么;alarm episode 要回答当前是否需要响应;condition 与 workflow 要分轴;action/audit 要回答谁做了什么;notification 要独立表达是否送达。只有边界明确,去重、关联、搁置和抑制才不会以删除证据为代价。

落地顺序可以很小:先保留稳定 event ID、三类时间和规则版本;再建立 episode 与受控迁移;随后补 action/audit、责任范围和通知记录;最后才增加抑制、根因关联和性能治理。对于已经把所有对象放在一张表的平台,优先新增不可变事件与 action 日志,再把现有 status 拆成 condition/workflow 两条轴。不要先做大迁移并覆盖历史;先让一条真实告警能被重放、解释和回滚。

如果你正在设计相邻的数据边界,可结合工业遥测数据链路分层工业 Tag 模型治理一起审查:遥测链路保留观测事实,Tag 合同稳定测量语义,告警模型承担可操作异常和人员处置,三者不应互相替代。

参考资料与证据边界

星野云联微信二维码