闹钟通知功能
闹钟通知功能
本节介绍如何在 ESP32 音频播放系统中实现告警通知功能。学习完成后,您将能够:
- 设计工厂广播系统中的告警优先级机制
- 实现告警音频的播放逻辑
- 通过 MQTT 触发和处理告警通知
- 理解告警打断和恢复常规广播的流程
在开始本节之前,请确保:
- 已完成 MQTT 远程控制配置
- 已实现播放状态管理
- 了解基本的优先级队列概念
Alarm Priority System
Section titled “Alarm Priority System”Priority Levels
Section titled “Priority Levels”enum AlarmPriority { PRIORITY_LOW = 0, // 一般通知(换班提醒等) PRIORITY_MEDIUM = 1, // 重要通知(生产异常等) PRIORITY_HIGH = 2, // 紧急告警(设备故障等) PRIORITY_CRITICAL = 3 // 危急告警(火灾、安全事件)};| 优先级 | 描述 | 行为 | 示例 |
|---|---|---|---|
| LOW | 一般通知 | 不打断当前播放,显示通知文字 | 换班提醒、午休通知 |
| MEDIUM | 重要通知 | 降低背景音乐音量并播报通知 | 设备维护提醒、生产异常 |
| HIGH | 紧急告警 | 打断当前播放,强制播报警告音频 | 温度超限、设备故障 |
| CRITICAL | 危急告警 | 最高优先级,持续报警直到确认 | 火灾报警、紧急疏散 |
Alarm Priority Manager
Section titled “Alarm Priority Manager”class AlarmManager {private: struct AlarmEvent { AlarmPriority priority; String audioURL; // 告警音频文件 URL String message; // 告警文本消息 unsigned long timestamp; bool requiresAck; // 是否需要人工确认 bool isActive; };
AlarmEvent currentAlarm; AlarmPriority currentPriority; bool alarmPlaying; unsigned long alarmStartTime;
public: AlarmManager() : currentPriority(PRIORITY_LOW), alarmPlaying(false) {}
// 触发告警 bool triggerAlarm(AlarmPriority priority, const String& url, const String& msg, bool requireAck = false) { // 更高优先级才打断当前告警 if (alarmPlaying && priority <= currentPriority) { return false; }
currentAlarm = {priority, url, msg, millis(), requireAck, true}; currentPriority = priority;
// 保存当前播放状态 saveCurrentState();
// 停止当前播放 stopCurrentPlayback();
// 播放告警音频 playAlarmAudio(url); alarmPlaying = true; alarmStartTime = millis();
Serial.printf("🚨 告警触发! [%d] %s\n", priority, msg.c_str()); return true; }
// 确认告警 void acknowledgeAlarm() { if (!currentAlarm.requiresAck) return;
currentAlarm.isActive = false; alarmPlaying = false;
// 恢复常规播放 restorePreviousState();
Serial.println("✅ 告警已确认"); }
// 自动恢复(限时告警自动结束) void checkAutoRecovery() { if (!alarmPlaying) return;
// 告警播放超过 30 秒自动恢复 if (millis() - alarmStartTime > 30000 && !currentAlarm.requiresAck) { alarmPlaying = false; currentAlarm.isActive = false; restorePreviousState(); Serial.println("⏱️ 告警自动恢复"); } }
bool hasActiveAlarm() { return alarmPlaying; } AlarmPriority getActivePriority() { return currentPriority; }};Alarm Audio Sources
Section titled “Alarm Audio Sources”Dual-URL Playback System
Section titled “Dual-URL Playback System”实现一个双源播放系统,在常规广播和告警播放之间切换:
// 告警音频列表const char* alarmAudioURLs[] = { "http://fileserver/audio/换班提醒.mp3", // LOW "http://fileserver/audio/设备异常.mp3", // MEDIUM "http://fileserver/audio/紧急情况.mp3", // HIGH "http://fileserver/audio/紧急疏散.mp3" // CRITICAL};
// TTS 动态生成告警语音// Node-RED 端可以使用 text-to-speech 节点生成自定义告警语音// 例如: TTS 生成 "车间A温度已超过安全阈值,请立即检查"MQTT Alarm Trigger
Section titled “MQTT Alarm Trigger”// MQTT Topic: factory/broadcast/alarm// Payload 格式:// {// "priority": 2,// "type": "temperature_alert",// "message": "车间A温度超过安全阈值:45°C",// "audio_url": "http://fileserver/alerts/temp_alert.mp3",// "requires_ack": true// }
void handleAlarmMessage(const char* jsonPayload) { StaticJsonDocument<256> doc; DeserializationError error = deserializeJson(doc, jsonPayload);
if (error) { Serial.printf("告警 JSON 解析失败: %s\n", error.c_str()); return; }
AlarmPriority priority = (AlarmPriority)doc["priority"]; const char* message = doc["message"] | ""; const char* audioURL = doc["audio_url"] | ""; bool requiresAck = doc["requires_ack"] | false;
alarmManager.triggerAlarm(priority, audioURL, message, requiresAck);}
// 在 mqttCallback 中订阅// mqttClient.subscribe("factory/broadcast/alarm");Alarm Confirmation and Escalation
Section titled “Alarm Confirmation and Escalation”Acknowledge Timeout and Escalation
Section titled “Acknowledge Timeout and Escalation”// 告警确认倒计时和升级逻辑class AlarmEscalation {private: unsigned long triggerTime; int currentLevel; bool resolved;
const unsigned long ESCALATION_INTERVALS[] = { 30000, // 30秒未确认 → 升级 60000, // 1分钟未确认 → 再次升级 120000 // 2分钟未确认 → 最高升级 };
public: void startAlarm() { triggerTime = millis(); currentLevel = 0; resolved = false;
// 开启确认超时检查 Serial.printf("告警确认超时检查已启动 (等级 %d)\n", currentLevel); }
void checkEscalation() { if (resolved) return;
unsigned long elapsed = millis() - triggerTime;
for (int i = currentLevel; i < 3; i++) { if (elapsed > ESCALATION_INTERVALS[i]) { currentLevel = i + 1;
// 升级通知 String escalationMsg = String("{\"event\":\"escalation\",") + "\"level\":" + String(currentLevel) + "," + "\"timeout_ms\":" + String(elapsed) + "}";
mqttClient.publish("factory/broadcast/alarm/escalation", escalationMsg.c_str());
Serial.printf("⚠️ 告警升级到等级 %d\n", currentLevel); } } }
void markResolved() { resolved = true; }};Node-RED Alarm Flow
Section titled “Node-RED Alarm Flow”Node-RED Alarm Processing
Section titled “Node-RED Alarm Processing”在 Node-RED 中配置告警逻辑:
// 温度传感器 → 告警判断 → MQTT 触发流程:1. MQTT In: factory/zone1/temperature (传感器数据)2. Function: checkThreshold - 如果 temperature > 40°C: - 构建告警 JSON - 发送到广播系统3. MQTT Out: factory/broadcast/alarm (触发广播)4. MQTT Out: factory/notification/telegram (发送消息通知)Node-RED Function 节点示例:
// 检查温度阈值并触发告警const TEMP_THRESHOLD = 40;const temp = msg.payload.temperature;
if (temp > TEMP_THRESHOLD) { return { payload: { priority: 2, type: "temperature_alert", message: `车间A温度异常: ${temp}°C (阈值: ${TEMP_THRESHOLD}°C)`, audio_url: "http://media-server/alerts/high_temp.mp3", requires_ack: true, timestamp: Date.now() }, topic: "factory/broadcast/alarm" };}return null;问题 1: 告警音频播放完毕后无法恢复常规广播
Section titled “问题 1: 告警音频播放完毕后无法恢复常规广播”症状: 告警播放结束后,常规广播没有自动恢复
原因: 状态恢复逻辑未正确实现
解决方案:
// 在音频 loop() 中检测告警播放结束void audioLoop() { if (alarmManager.hasActiveAlarm()) { if (decoder && !decoder->isRunning()) { // 告警音频播放完毕,自动恢复 alarmManager.acknowledgeAlarm(); } alarmManager.checkAutoRecovery(); } else { // 常规播放 if (decoder && decoder->isRunning()) { if (!decoder->loop()) { handleStreamEnd(); } } }}Summary
Section titled “Summary”本节介绍了告警通知功能的实现:
- 优先级系统:四级优先级(LOW → CRITICAL),更高优先级打断低优先级
- 告警触发:通过 MQTT JSON 消息触发告警,指定优先级、音频源和消息
- 确认机制:支持人工确认和定时自动恢复
- 告警升级:未及时确认的告警自动升级通知
- Node-RED 集成:传感器数据 → 阈值判断 → 告警触发 → 语音广播