报警需求

工业监测系统需要在以下情况发出报警:

  • 瞬时流量超上限 / 低于下限
  • 压力超上限
  • 任意 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);

联调时用这个命令验证整个报警链路:注入 → 队列 → 上行编码 → 串口发送 → 云端接收。

小结

报警系统的关键设计:

  • 边沿触发:只在状态变化时报警,避免刷屏
  • 队列解耦:采集线程不等发送完成,立即返回
  • 即时上报:上行线程收到报警事件后立即发送,不等遥测周期
  • 严重性分级:云端可以按级别做不同的处理策略

后续阅读