报警需求
工业监测系统需要在以下情况发出报警:
- 瞬时流量超上限 / 低于下限
- 压力超上限
- 任意 PT100 通道温度超上限
- 关键传感器连续无效
- MCU 重启
报警需要即时上报到云端,不能等下一个 10 秒遥测周期。
报警事件结构
typedef struct
{
app_alarm_code_t code; /* 报警码 */
app_alarm_severity_t severity; /* 严重性 */
float value; /* 触发时的测量值 */
rt_uint8_t has_value; /* 状态类报警无值 */
time_t timestamp;
} app_alarm_event_t;
报警码枚举:
typedef enum
{
APP_ALARM_CODE_OVER_FLOW = 1, /* 流量超上限 */
APP_ALARM_CODE_UNDER_FLOW, /* 流量低下限 */
APP_ALARM_CODE_OVER_PRESSURE, /* 压力超上限 */
APP_ALARM_CODE_OVER_TEMP, /* 温度超上限 */
APP_ALARM_CODE_SENSOR_FAULT, /* 传感器故障 */
APP_ALARM_CODE_MCU_RESTART, /* MCU 重启 */
} app_alarm_code_t;
严重性分级:
typedef enum
{
APP_ALARM_SEVERITY_INFO = 0, /* 信息:MCU 重启 */
APP_ALARM_SEVERITY_WARN, /* 警告:超阈值 */
APP_ALARM_SEVERITY_CRITICAL, /* 严重:温度过高 */
} app_alarm_severity_t;
为什么不能用”电平触发”
最简单的报警逻辑是:每次采样后检查阈值,超了就发报警。
问题是:如果流量持续超限,每 2 秒采样一次,每 2 秒就会发一条报警。10 分钟就是 300 条。云端会被淹没,真正的故障反而被淹没了。
边沿触发
解决方案:只在状态变化的边沿发报警。
用静态变量记录上一轮的触发状态:
static void app_alarm_evaluate(const app_measurement_t *m)
{
static rt_uint8_t s_over_flow = 0U; /* 上一轮状态 */
rt_uint8_t now_over_flow = 0U; /* 本轮状态 */
if (m->flow_valid && m->flow_lpm > APP_ALARM_FLOW_MAX_LPM)
now_over_flow = 1U;
/* 只在 0→1 的边沿发报警 */
if (now_over_flow && !s_over_flow)
app_alarm_emit(APP_ALARM_CODE_OVER_FLOW, APP_ALARM_SEVERITY_WARN, m->flow_lpm);
s_over_flow = now_over_flow; /* 更新状态 */
}
这样:
- 流量从正常变超限 → 发一条报警
- 持续超限 → 不再发
- 恢复正常 → 不发(只关注故障发生,不关注恢复)
阈值配置
当前采用固化阈值,后续 Phase 6 会做云端下发和持久化:
#define APP_ALARM_FLOW_MAX_LPM 200.0f /* 瞬时流量上限 */
#define APP_ALARM_FLOW_MIN_LPM 0.0f /* 下限(0=禁用) */
#define APP_ALARM_PRESSURE_MAX_KPA 700.0f /* 压力上限 */
#define APP_ALARM_TEMP_MAX_DECIC 800 /* 温度上限:80.0℃ */
温度阈值用 *10 ℃ 的原始单位,避免浮点比较的开销。
报警队列
报警事件通过消息队列传递给上行线程:
#define APP_ALARM_QUEUE_DEPTH 16U
static struct rt_messagequeue g_alarm_mq;
投递函数:
static void app_alarm_emit(app_alarm_code_t code,
app_alarm_severity_t sev,
float value)
{
app_alarm_event_t evt;
evt.code = code;
evt.severity = sev;
evt.value = value;
evt.has_value = 1U;
evt.timestamp = time(RT_NULL);
if (rt_mq_send(&g_alarm_mq, &evt, sizeof(evt)) == RT_EOK)
rt_event_send(&g_app_event, APP_EVENT_ALARM_TRIGGERED);
}
队列满了就丢弃——这种情况极少发生,而且丢一条报警比阻塞采集线程好。
上行线程的即时消费
上行线程监听 APP_EVENT_ALARM_TRIGGERED 事件,收到后立即清空队列里的所有报警:
static void drain_alarms(void)
{
app_alarm_event_t evt;
while (app_alarm_recv(&evt, 0) == RT_EOK) /* 非阻塞 */
{
n = uplink_encode_alarm(buf, sizeof(buf), ++g_tx_seq, &evt);
if (n > 0)
{
send_frame_wait_cloud_ack(buf, n, seq, RT_TRUE);
g_stat.alarm_sent++;
}
}
}
报警帧不等 10 秒周期,立即发送立即等 ACK。
状态类报警:MCU 重启
有些报警不是基于测量值的,比如 MCU 重启。这类报警用 has_value = 0 表示:
static void app_alarm_emit_state(app_alarm_code_t code,
app_alarm_severity_t sev)
{
app_alarm_event_t evt;
evt.code = code;
evt.severity = sev;
evt.value = 0.0f;
evt.has_value = 0U; /* 无值 */
evt.timestamp = time(RT_NULL);
rt_mq_send(&g_alarm_mq, &evt, sizeof(evt));
}
系统启动时主动发一条 MCU_RESTART 报警,让云端知道设备曾经重启过。
报警帧的 JSON 格式
带值的报警:
{"t":"alarm","ts":1712345678,"seq":124,"dev":"FM001",
"code":"OVER_FLOW","val":150.000,"severity":"warn"}
无值的状态报警:
{"t":"alarm","ts":1712345678,"seq":125,"dev":"FM001",
"code":"MCU_RESTART","severity":"info"}
msh 调试命令
可以手动注入一条测试报警:
static void cmd_uplink_test_alarm(int argc, char **argv)
{
app_alarm_event_t evt;
evt.code = APP_ALARM_CODE_OVER_FLOW;
evt.severity = APP_ALARM_SEVERITY_WARN;
evt.value = 150.0f;
evt.has_value = 1U;
evt.timestamp = time(RT_NULL);
app_alarm_post(&evt);
}
MSH_CMD_EXPORT_ALIAS(cmd_uplink_test_alarm, uplink_test_alarm, inject a fake alarm);
联调时用这个命令验证整个报警链路:注入 → 队列 → 上行编码 → 串口发送 → 云端接收。
小结
报警系统的关键设计:
- 边沿触发:只在状态变化时报警,避免刷屏
- 队列解耦:采集线程不等发送完成,立即返回
- 即时上报:上行线程收到报警事件后立即发送,不等遥测周期
- 严重性分级:云端可以按级别做不同的处理策略