紧急呼叫报警器

Copyright © Quectel Wireless Solutions Co., Ltd. 2026. All rights reserved.


紧急报警器方案是一个基于UniRTOS构建的紧急报警与求助通知方案。

本项目以UniRTOS作为核心运行平台,结合按键触发、语音唤醒、短信通知和多联系人拨号轮询机制,在移远EG800Z模块上完成报警触发、联系人通知与紧急求助信息发送。系统支持按键触发和语音唤醒两种报警方式,报警后可自动向所有紧急联系人发送报警短信;同时支持配置多紧急联系人,当第一联系人电话拨号失败时,可自动轮询下一联系人,直至完成通知。依托API接口丰富的UniRTOS,方案具备良好的跨模块适配能力,便于在不同移远模块上运行,适用于便携应急报警、老人看护、个人安全防护及移远模块快速集成等场景。

开发资源

配件名称

数量

简要描述

获取方法

QuecDuino开发板

1

项目运行硬件平台,需搭载模块型号“EG800ZCN_LH/EG800ZEU_LH”。

点此购买

USB数据线

1

用于连接PC和开发板,非“仅充电”类型USB-A转USB-C。

点此购买

PC

1

系统为Windows 10/Windows 11。

自行准备

SIM卡

1

可正常上网的USIM卡。

自行准备

喇叭

1

功率 2-5 W的喇叭

点此购买

LED模块

1

GPIO驱动的LED模块

点此购买

蜂鸣器模块

1

GPIO驱动的蜂鸣器模块

点此购买

快速上手

硬件连接

../../_images/image_C1ChbmSS6oFXNExOnVhcOpUSnxp.webp
  1. 将天线嵌入开发板丝印为“LTE”的天线底座。

  2. ASRPRO模块连接开发板UART0(RX:Pin38,TX:Pin39),注意RX->TX、TX->RX。规格书:点此获取。

  3. 蜂鸣器模块可连接任意支持GPIO的引脚(默认Pin25)。GPIO映射表:点此获取。

  4. LED模块可连接任意支持GPIO的引脚(默认Pin23)。或直接使用开发板上的LED。GPIO映射表:点此获取。

  5. 机械按键可连接任意支持GPIO的引脚(默认Pin29),或直接使用开发板上的按键。GPIO映射表:点此获取。

  6. 喇叭连接开发板丝印为“-spk+”的两个引脚。位置可参照规格书:点此获取。

  7. 使用数据线连接开发板和电脑。

软件部署

开发环境搭建

参考 UniRTOS快速上手 文档,了解如何搭建开发环境并完成基本开发流程。

代码拉取

新开启一个PowerShell窗口,执行以下命令:

# 拉取示例仓库
unirtos-cli new -r unirtos-maker-examples
# 进入该项目
cd unirtos-maker-examples/emergency_call_alarm

修改配置参数

云平台配置参数

在 alarm_config.h 中配置云平台相关参数。关于阿里云平台产品创建与参数获取,可参考 AliYun平台 说明。

../../_images/image_Dp92bVYLWoBMlZxtoVycLiphnXb.webp

联系人信息配置

云平台未下发联系人信息时,可在 alarm_config.h 中配置临时使用的联系人信息。

../../_images/image_R1cKbrB2Oomvybxf9m5cnB8fnag.webp

构建项目

在开启的PowerShell窗口继续执行环境初始化命令:

unirtos-cli env-setup

在PowerShell窗口执行固件编译命令(如使用模块型号非EG800ZCN_LH,请替换实际需要编译的型号):

unirtos-cli build -m EG800ZCN_LH -v EG800ZCN_LH_BETA_20260904

等待编译结束后,PowerShell窗口末尾会提示固件编译结果:

SUCCESS: Unirtos project built successfully!

固件编译成功后通过QFlash烧录到开发板中。

板上验证

  1. 长按开发板电源键(S1)开机,打开EPAT工具,根据 日志调试教程 进行配置,配置完成后重启开发板,从模块上电开始抓取模块日志,确保日志输出完整。

../../_images/image_STTobLkkXoiqSpxfkUMcVSdan2e.webp
  1. 在云平台下发实际需要的JSON格式的联系人信息,重启开发板以应用新的联系人信息。格式参考:

{"contacts":[{"name":"NAME1","phone":"135xxxxxxxx"},{"name":"NAME2","phone":"132xxxxxxxxx"}]} 
../../_images/image_Lg36bZdPBo2x2qxEApEcofyJnib.webp
  1. 按下按键或语音唤醒(唤醒词默认为“救命”)触发报警,可组合多种情况进行测试,如第一联系人接通/失败,第二联系人接通/失败等,测试完成后,在EPAT搜索“[alarm]”查看运行日志。

../../_images/image_XdFabgbpDoEpm4x7VzVcH7Xtnyd.webp

效果说明

项目在三种状态下的现象如下:

  • 空闲:LED模块呼吸灯效果,蜂鸣器关闭。

  • 拨号中:报警触发直至接通阶段,LED快速闪烁,蜂鸣器开启。

  • 通话中:电话接通状态下,LED和蜂鸣器均关闭。

报警触发和所有联系人拨号失败两种情况下,会有TTS播报提醒。

软件设计详解

方案框架

软件划分为六层:

  1. 核心逻辑层:core.c 维护报警会话状态机,是业务决策中心。

  2. 中介模块:app.c 仅传递低频控制事件,不承载业务逻辑。

  3. 服务层:service/call_sms.c 提供IMS呼叫、短信群发和Codec链路准备能力。

  4. 反馈层:feedback/indicator.c 根据状态切换LED、蜂鸣器与TTS播报。

  5. 外设层:machine/key.c 与 asr_pro.c 把按键中断、语音关键词转换成统一事件。

  6. 云配置层:cloud/ 负责联系人下发、JSON解析与NV持久化。

业务流程

../../_images/board_Ze4TwW80RhK2lKbfLtrctKRznKe.jpg

启动流程

image

启动流程说明

系统启动后,应用入口函数被自动注册并执行。alarm_init() 负责创建主任务和事件消息队列。alarm_task() 先初始化状态机、反馈层与外设服务,再进入循环。在循环中,程序持续等待事件;若有事件,则交给状态机处理;若没有事件,则执行周期tick。这样既能及时响应触发事件,也不会耗尽CPU资源。

代码讲解

项目结构

main/
├── inc/
│   ├── alarm_app.h            # 模块公共接口声明
│   ├── alarm_config.h         # 引脚、超时、联系人等配置
│   ├── alarm_es8311_codec.h   # ES8311 Codec 接口声明
│   └── alarm_types.h          # 事件、状态、上下文类型定义
└── src/
    ├── main.c                 # 应用入口 unir_alarm_demo_init
    └── alarm/
        ├── app.c              # alarm_init/alarm_task,任务与队列创建
        ├── core.c             # 状态机 core,事件处理与拨号轮询
        ├─ cloud/
            ├─ mqtt_client.c   # 阿里云MQTT连接、订阅、收包、回执
            ├─ contact_store.c # 下发JSON解析、字段校验与序列化
            └─ config_parser.c # 联系人双缓冲存储与NV持久化
        ├── feedback/
        │   └── indicator.c    # LED/蜂鸣器/震动/TTS/电池反馈
        ├── machine/
        │   ├── key.c          # SOS按键中断触发
        │   └── asr_pro.c      # ASR串口关键词触发
        └── service/
            └── call_sms.c     # IMS通话、短信发送、Codec初始化

初始化

项目入口位于 src/main.c 中的 unir_alarm_demo_init()。这个函数负责完成三件事:

  1. 设置GPIO电压;

  2. 打印启动日志;

  3. 调用 alarm_init() 创建报警应用任务和事件队列。

void unir_alarm_demo_init(void)
{
    if(qosa_gpio_set_voltage(VOL_3_30V) != QOSA_OK) {
        QLOGE("[alarm] failed to set GPIO voltage");
    }

    QLOGV("[alarm] enter alarm demo !!!");
    QLOGV("[alarm] sdk version(tag): %s", qosa_sdk_get_version());
    QLOGV("[alarm] fw version: %s", qosa_get_fw_version());

    alarm_init();
    qosa_task_sleep_sec(5);
}
UNIRTOS_APP_EXPORT(700, "unir_alarm_demo", unir_alarm_demo_init);

这里使用了UniRTOS的应用注册机制 UNIRTOS_APP_EXPORT(),把应用启动函数注册到系统初始化列表中,由框架统一调度。这种方式比手工维护初始化列表更稳定,也更方便后续扩展新模块。

唤醒与触发

报警器的输入层由SOS按键和ASR语音识别共同构成,最终都会统一转成事件投递到状态机中。

static void sos_isr(void *argv)
{
    alarm_event_t event = {0};

    QOSA_UNUSED(argv);
    event.type = ALARM_EVENT_SOS_EDGE;
    (void)alarm_post_event(&event);
}
if ((strstr((const char *)buffer, "SOS") != QOSA_NULL) ||
    (strstr((const char *)buffer, "help") != QOSA_NULL) ||
    (strstr((const char *)buffer, "CALL") != QOSA_NULL))
{
    post_voice_trigger();
}

当按键中断或ASR串口命中关键词后,程序不会直接改核心状态,而是发出统一事件,再由状态机统一做后续处理。这样可以减少不同输入通道之间的耦合。

状态机与呼叫逻辑

报警状态机位于 src/alarm/core.c,核心逻辑是处理事件并推进会话状态。其入口函数是 alarm_core_handle_event(),真正的呼叫入口是 begin_alert() 和 start_next_call()。

static void begin_alert(alarm_ctx_t *ctx, alarm_trigger_source_e trigger)
{
    if (ctx->session.active == QOSA_TRUE)
    {
        return;
    }

    ctx->session.active = QOSA_TRUE;
    ctx->session.current_contact_index = 0;
    set_state(ctx, ALARM_STATE_ALERT_PENDING);
    start_next_call(ctx);
}
static void start_next_call(alarm_ctx_t *ctx)
{
    const alarm_contact_t *contacts = QOSA_NULL;
    qosa_uint8_t           contact_count = 0;
    int                    ret = 0;

    contacts = alarm_get_contacts(&contact_count);

    if (ctx->session.current_contact_index >= contact_count)
    {
        fail_session(ctx);
        return;
    }

    set_state(ctx, ALARM_STATE_CALLING);
    ret = service_start_call(contacts[ctx->session.current_contact_index].phone_number);
    if (ret != 0)
    {
        ctx->session.current_contact_index++;
        start_next_call(ctx);
        return;
    }

    ctx->session.call_remaining_ms = ALARM_CALL_TIMEOUT_MS;
}

这里体现了报警器的关键策略:

  • 接收到触发后,状态切到报警准备阶段;

  • 按联系人顺序发起呼叫;

  • 单个联系人超时或者断开后,自动轮询下一位联系人;

  • 全部联系人耗尽后,进入失败态并自动恢复。

业务服务层

服务层位于 src/alarm/service/call_sms.c,负责IMS通话、短信发送和Codec配置。它主要做两类工作:

  • 把底层IMS事件映射成报警器内部事件;

  • 通过SDK接口发起呼叫和发送短信。

static int ims_event_cb(void *user_argv, void *argv)
{
    alarm_event_t      event = {0};
    qosa_notify_event_e event_id = (qosa_notify_event_e)(qosa_ptr)user_argv;

    switch (event_id)
    {
        case QOSA_EVENT_MODEM_IMS_RING_STATUS:
            event.type = ALARM_EVENT_CALL_RING;
            break;

        case QOSA_EVENT_MODEM_IMS_CONN_ID_EVNET:
            event.type = ALARM_EVENT_CALL_CONNECTED;
            break;

        case QOSA_EVENT_MODEM_IMS_DISCONNECT_STATUS:
            event.type = ALARM_EVENT_CALL_DISCONNECTED;
            break;

        default:
            return 0;
    }

    (void)alarm_post_event(&event);
    return 0;
}
int service_start_call(const char *phone_number)
{
    if (phone_number == QOSA_NULL)
    {
        return -1;
    }

    return qosa_start_voice_call(phone_number);
}

短信逻辑则通过 service_start_sms_fanout() 循环给联系人发送报警短信,同时不会阻塞主状态机运行。

反馈层

反馈层由 src/alarm/feedback/indicator.c 负责,主要针对状态可视化与用户感知进行输出。它支持:

  • LED呼吸、快闪和常亮模式切换

  • 蜂鸣器

  • TTS语音播报

  • 低电量轮询与告警

static void set_state(alarm_ctx_t *ctx, alarm_state_e state)
{
    if (ctx->session.state == state)
    {
        return;
    }

    ctx->session.state = state;
    feedback_apply_state(state);
}
void feedback_apply_state(alarm_state_e state)
{
    switch (state)
    {
        case ALARM_STATE_IDLE:
            set_led_blink_mode(LED_BLINK_MODE_BREATH);
            break;

        case ALARM_STATE_ALERT_PENDING:
        case ALARM_STATE_CALLING:
        case ALARM_STATE_CALL_STOPPING:
            set_led_blink_mode(LED_BLINK_MODE_FAST);
            break;

        case ALARM_STATE_CONNECTED:
            set_led_blink_mode(LED_BLINK_MODE_SOLID_ON);
            break;

        case ALARM_STATE_FAILED:
            set_led_blink_mode(LED_BLINK_MODE_OFF);
            break;

        default:
            break;
    }
}

反馈逻辑和状态机紧耦合,状态一切换,对应输出就会切换,从而让用户能直观看到当前报警器处于什么阶段。

云平台配置通道

云配置能力位于 src/alarm/cloud/ 目录,拆成三个职责单一的文件:mqtt_client.c 负责阿里云连接与收包,config_parser.c 负责JSON解析校验,contact_store.c 负责联系人存储与NV持久化。

JSON格式示例:

//下发示例
{  "contacts": [    
    {"name": "NAME1", "phone": "138xxxxxxxx"},    
    {"name": "NAME2", "phone": "138xxxxxxxx"}]
}
//回执示例
{"result":"OK","count":2,"persisted":1}

云端下发的报文先经过解析校验,任一条目非法就整包拒绝,不会写入脏数据。

static void cloud_handle_contacts_payload(const char *payload, qosa_uint32_t payload_len)
{
    alarm_contact_t parsed[ALARM_CONTACT_MAX] = {0};
    alarm_event_t   event = {0};
    qosa_uint8_t    count = 0;
    const char     *reason = QOSA_NULL;
    int             store_ret = 0;

    if (alarm_contacts_parse_json(json, parsed, &count, &reason) != 0)
    {
        cloud_publish_ack(QOSA_FALSE, 0, QOSA_FALSE, reason);
        return;
    }

    store_ret = alarm_contact_store_update(parsed, count);
    if (store_ret < 0)
    {
        cloud_publish_ack(QOSA_FALSE, 0, QOSA_FALSE, "store_error");
        return;
    }

    event.type = ALARM_EVENT_CONTACTS_UPDATED;
    event.data.contacts.count = count;
    (void)alarm_post_event(&event);

    cloud_publish_ack(QOSA_TRUE, count, (store_ret == 0) ? QOSA_TRUE : QOSA_FALSE, QOSA_NULL);
}

联系人存储采用双缓冲:写方先填非活动缓冲,填完再翻转下标,读方无锁读取活动缓冲,避免拨号过程中联系人表被半途改写。

static void publish_to_standby(const alarm_contact_t *list, qosa_uint8_t count)
{
    qosa_uint8_t standby = (qosa_uint8_t)(g_active_idx ^ 1u);

    qosa_memset(g_contacts[standby], 0, sizeof(g_contacts[standby]));
    qosa_memcpy(g_contacts[standby], list, sizeof(alarm_contact_t) * count);
    g_counts[standby] = count;
    g_active_idx = standby;
}

状态机收到配置更新事件后,只在空闲态才刷新会话联系人数,报警进行中则推迟到下一次生效。

应用演示

第一次运行请先修改代码中的联系人信息,重新编译烧录新固件。或在代码中配置云平台信息,通过云平台下发联系人信息进行更新。

报警触发

通过按键或语音成功触发后,此时指示灯将快速闪烁,表示进入报警状态。

../../_images/image_QvZ9bV1XuoMM4nxMo0ycD7PanDh.webp

群发短信

报警触发后,程序将发送预设好的短信内容(可在代码中修改为自定义内容)通知所有联系人。

../../_images/image_N7LybPBiLoqJG1xVxHpc3pUbnQh.webp

电话呼叫

群发短信后,程序将按照预设的联系人顺序,依次对各个联系人进行电话呼叫。

../../_images/image_G5cgblbo1oopkYxZBD2cOMlpnrb.webp