跳转到内容

闹钟通知功能

闹钟通知功能

本节介绍如何在 ESP32 音频播放系统中实现告警通知功能。学习完成后,您将能够:

  • 设计工厂广播系统中的告警优先级机制
  • 实现告警音频的播放逻辑
  • 通过 MQTT 触发和处理告警通知
  • 理解告警打断和恢复常规广播的流程

在开始本节之前,请确保:

  • 已完成 MQTT 远程控制配置
  • 已实现播放状态管理
  • 了解基本的优先级队列概念
enum AlarmPriority {
PRIORITY_LOW = 0, // 一般通知(换班提醒等)
PRIORITY_MEDIUM = 1, // 重要通知(生产异常等)
PRIORITY_HIGH = 2, // 紧急告警(设备故障等)
PRIORITY_CRITICAL = 3 // 危急告警(火灾、安全事件)
};
优先级描述行为示例
LOW一般通知不打断当前播放,显示通知文字换班提醒、午休通知
MEDIUM重要通知降低背景音乐音量并播报通知设备维护提醒、生产异常
HIGH紧急告警打断当前播放,强制播报警告音频温度超限、设备故障
CRITICAL危急告警最高优先级,持续报警直到确认火灾报警、紧急疏散
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; }
};

实现一个双源播放系统,在常规广播和告警播放之间切换:

// 告警音频列表
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 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");
// 告警确认倒计时和升级逻辑
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 中配置告警逻辑:

// 温度传感器 → 告警判断 → 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();
}
}
}
}

本节介绍了告警通知功能的实现:

  1. 优先级系统:四级优先级(LOW → CRITICAL),更高优先级打断低优先级
  2. 告警触发:通过 MQTT JSON 消息触发告警,指定优先级、音频源和消息
  3. 确认机制:支持人工确认和定时自动恢复
  4. 告警升级:未及时确认的告警自动升级通知
  5. Node-RED 集成:传感器数据 → 阈值判断 → 告警触发 → 语音广播