跳到主要内容

报警推送

车间报警触发后需要第一时间通知合适的人, 并避免"狼来了"疲劳。DarraRT 报警推送分两层:

  1. 本地层 (HMI): 浏览器 Notification / Web TTS / 铃声 / 视觉滚动
  2. 外部层 (Service 代理): 邮件 / 企业微信 / 飞书 / 钉钉 / 短信

注: 本章只涉及设备级报警, 不处理业务报警 (库存超限/订单延期等由其他系统负责)。

报警分级

PLC 侧定义报警时必须标注 Severity 字段, 驱动推送策略:

级别严重度颜色典型场景推送目标
Info0启停/换型/换班仅日志
Warn1温度接近上限 / 备件寿命剩 <10%本地通知
Error2工艺偏差 / 单工位故障本地 + 外部 (工程师)
Fatal3急停 / 设备损坏 / 数据库断开全员 + 电话升级 + 自动停机

PLC 声明报警 UDT:

TYPE T_Alarm :
STRUCT
Code : DINT; (* 唯一编号, 例如 1001 *)
Severity : INT; (* 0..3 *)
Active : BOOL;
Acked : BOOL;
Message : STRING[128];
Source : STRING[32]; (* 设备/工位 *)
FirstTime : DT;
LastTime : DT;
END_STRUCT
END_TYPE

本地层: HMI 触发

订阅报警事件

darra.on('alarm', ...) 接收所有活动报警:

darra.on('alarm', (alarm) => {
// alarm: { code, severity, message, source, firstTime, acked }
console.log('收到报警:', alarm);

handleAlarm(alarm);
});

function handleAlarm(a) {
switch (a.severity) {
case 0: logOnly(a); break;
case 1: showNotification(a); playSound('warn'); break;
case 2: showNotification(a); playSound('error');
requestExternal(a); break;
case 3: showNotification(a); playSound('fatal');
speakTTS(a); requestExternal(a);
requireAck(a, 300); break;
}
}

浏览器 Notification API

推荐在登录成功后申请授权:

if (Notification.permission === 'default') {
const p = await Notification.requestPermission();
if (p !== 'granted') {
showBanner('提示: 浏览器通知被禁用, 报警只能在页面内显示');
}
}

function showNotification(a) {
if (Notification.permission !== 'granted') return;

new Notification(`[${severityLabel(a.severity)}] ${a.source}`, {
body: a.message,
icon: `/static/img/alarm-${a.severity}.png`,
tag: `alarm-${a.code}`, // 同 code 的通知自动替换
requireInteraction: a.severity >= 2 // 严重以上必须手动关闭
});
}

Web TTS 语音播报

function speakTTS(a) {
if (!window.speechSynthesis) return;

const u = new SpeechSynthesisUtterance(
`${severityLabel(a.severity)}: ${a.source} ${a.message}`
);
u.lang = 'zh-CN';
u.rate = 0.9;
u.volume = 1.0;

speechSynthesis.cancel(); // 打断前一条
speechSynthesis.speak(u);
}

音频铃声

不同级别不同铃声, 且可循环直到确认:

<audio id="snd-warn"  src="/static/sound/warn.mp3"></audio>
<audio id="snd-error" src="/static/sound/error.mp3"></audio>
<audio id="snd-fatal" src="/static/sound/fatal.mp3" loop></audio>
function playSound(kind) {
const el = document.getElementById(`snd-${kind}`);
if (el) {
el.currentTime = 0;
el.play().catch(e => console.warn('audio play failed:', e));
}
}

function stopFatalSound() {
document.getElementById('snd-fatal').pause();
}

Fatal 铃声 loop 循环直到 PLC 侧 Alm.Ack 置 TRUE 后前端停止:

darra.bind('M.Alm.Ack', v => { if (v) stopFatalSound(); });

可视化列表

<darra-alarm-list> 自带排序、颜色、滚动:

<darra-alarm-list max-rows="12"
severity-filter="1,2,3"
sort="severity,firsttime">
</darra-alarm-list>

外部层: Service 代理推送

HMI 不直接调 SMTP / 钉钉 SDK (凭据放前端不安全), 而是把推送请求交给 Service:

HMI ──(内部通道)──> DarraRT Service ──(SMTP/Webhook/SMS)──> 邮箱/企业微信/钉钉/短信

Service 侧在 appsettings.json 配置通道凭据:

{
"AlarmPush": {
"Enabled": true,
"Channels": {
"email": {
"Host": "smtp.example.com", "Port": 587,
"User": "alarm@example.com", "Password": "***",
"From": "alarm@example.com"
},
"wechat_work": {
"Webhook": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=***"
},
"feishu": {
"Webhook": "https://open.feishu.cn/open-apis/bot/v2/hook/***"
},
"dingtalk": {
"Webhook": "https://oapi.dingtalk.com/robot/send?access_token=***",
"Secret": "***"
},
"sms": {
"Provider": "aliyun",
"AccessKey": "***", "AccessSecret": "***",
"SignName": "Darra", "TemplateCode": "SMS_xxx"
}
},
"Recipients": {
"Warn": ["ops-wechat"],
"Error": ["ops-wechat","shift-engineer-phone"],
"Fatal": ["ops-wechat","shift-engineer-phone","mgr-phone","factory-manager-email"]
}
}
}

PLC 可在报警触发时通过通讯 FB 调用 Service 的内部推送接口 (运行于 localhost, 无需外网):

PushAlarm(
Execute := AlarmJustRose,
Severity := AlarmRec.Severity,
Message := AlarmRec.Message,
Source := AlarmRec.Source
);

分级推送策略

级别本地铃声本地通知Web TTS邮件微信/飞书/钉钉短信电话升级自动停机
Info--------
WarnYesYes------
ErrorYesYes-YesYes---
FatalYesYesYesYesYesYesYesYes

"电话升级"指语音机器人拨打当班工程师电话直到接听, 由第三方 SaaS 提供 (如阿里云语音通知)。

静默窗口 (防刷屏)

同一 Source + Code 的报警在 3 分钟内 第二次触发时, 只更新"最后时间"与"出现次数", 不再推送外部通道:

[10:00:15] 锅炉A 温度超标 → 推送到企业微信
[10:01:22] 锅炉A 温度超标 → 仅更新计数, 不推送
[10:02:55] 锅炉A 温度超标 → 仅更新计数, 不推送
[10:04:10] 锅炉A 温度超标 → 距首发 > 3 min, 重新推送, 消息里标注"5 次"

Service 侧实现 (伪代码):

var key = $"{a.Source}:{a.Code}";
var last = _lastPush.GetOrDefault(key);
if (last != null && (DateTime.Now - last.Time) < TimeSpan.FromMinutes(3)) {
last.Count++;
return; // 静默
}
_lastPush[key] = new PushRecord { Time = DateTime.Now, Count = 1 };
Push(a);

确认与升级

Fatal 报警必须在 5 分钟内被确认, 否则自动升级到下一联系人:

T+0:00  Fatal 报警触发 → 通知当班工程师
T+5:00 未确认 → 升级: 同时通知班长
T+10:00 仍未确认 → 升级: 通知车间主任 + 电话
T+15:00 仍未确认 → 升级: 通知工厂经理

确认动作: PLC 侧 Alm.Ack = TRUE 后, Service 收到变量变更事件即取消升级定时器。

升级联系人配置:

{
"Escalation": {
"Fatal": [
{ "delay": "0m", "group": "shift-engineer" },
{ "delay": "5m", "group": "shift-leader" },
{ "delay": "10m", "group": "workshop-director" },
{ "delay": "15m", "group": "plant-manager", "call": true }
]
}
}

夜间模式

22:00 - 06:00 仅 Fatal 级报警 推送外部通道 (白天 Warn 也推送), 避免夜间非严重报警骚扰休息中的工程师:

{
"QuietHours": {
"Start": "22:00", "End": "06:00",
"MinSeverity": "Fatal",
"ExceptGroups": ["night-shift-engineer"]
}
}

值班工程师 night-shift-engineer 组不受静默, 保留所有推送。

审计链

每条报警从生成到关闭的完整时间链写入 Historian:

字段说明
AlarmId报警唯一 ID
Code报警代码
Severity级别
Source来源
Message消息文本
FirstTime首次触发时间
AckTime确认时间
AckBy确认人
EscalatedTo升级链 (数组)
ResetTime复位时间
ResetBy复位人
Duration持续时长

审计数据可导出为合规报表, 满足设备管理规范要求。

HMI 报警中心页面

推荐提供一个专用"报警中心"页面集中展示:

<div class="alarm-center" x-data="alarmCenter()">

<div class="kpi-bar">
<darra-numeric var="$.Alarm.ActiveCount" label="活动"></darra-numeric>
<darra-numeric var="$.Alarm.UnackedCount" label="未确认" big></darra-numeric>
<darra-numeric var="$.Alarm.TodayCount" label="今日合计"></darra-numeric>
</div>

<darra-alarm-list max-rows="50"
severity-filter="1,2,3"
columns="time,severity,source,code,message,acked"
allow-ack="true">
</darra-alarm-list>

<darra-alarm-chart period="24h" group-by="severity"></darra-alarm-chart>

</div>

$.Alarm.* 是 Service 提供的内置统计变量。

用户授权检查清单

  • 登录成功后引导用户授权 Notification
  • 说明浏览器可能屏蔽声音播放 (Chrome 需用户先交互一次)
  • 允许用户设置"推送免打扰时段" (个人偏好, 不影响系统级)
  • 允许禁用 Web TTS 防骚扰同事
  • 列出每个用户订阅的通道, 随时可退订

测试用例

每条报警规则上线前必测:

  1. 触发测试: 手工触发报警, 验证本地 + 外部推送都收到
  2. 静默测试: 连续触发 5 次, 3 分钟内只推送 1 次
  3. 升级测试: 触发 Fatal 不确认, 验证 5/10/15 分钟升级
  4. 确认终止: 升级链中途确认, 后续升级取消
  5. 夜间模式: 22:30 触发 Warn, 确认不推送外部, Fatal 正常推送
  6. 故障转移: 关 SMTP, Warn 降级为本地通知
  7. 审计完整: 每个事件均有 AlarmId / AckBy / ResetBy 记录

常见问题

现象原因处理
HMI 没响playSound 被浏览器拦截首次登录后引导用户点击一次
Notification 权限被永久拒绝浏览器设置浏览器设置里手动打开
微信推送收不到Webhook URL 失效 / IP 白名单检查 appsettings.json
钉钉推送失败签名算法错Secret + timestamp HMAC-SHA256
短信乱码模板未支持中文改用 Unicode 模板
报警刷屏静默窗口未启用打开 QuietWindowMinutes
升级未触发确认事件未收到检查 WebSocket 通路

相关文档