# 紧急呼叫报警器 ***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驱动的蜂鸣器模块 | [点此购买]() | # 快速上手 ## 硬件连接 ```{image} images/image_C1ChbmSS6oFXNExOnVhcOpUSnxp.webp :width: 785px :height: 579px ``` 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窗口,执行以下命令: ```plaintext # 拉取示例仓库 unirtos-cli new -r unirtos-maker-examples # 进入该项目 cd unirtos-maker-examples/emergency_call_alarm ``` ### 修改配置参数 **云平台配置参数** 在 *alarm_config.h* 中配置云平台相关参数。关于阿里云平台产品创建与参数获取,可参考 [AliYun平台]() 说明。 ```{image} images/image_Dp92bVYLWoBMlZxtoVycLiphnXb.webp :width: 1014px :height: 581px :align: center ``` **联系人信息配置** 云平台未下发联系人信息时,可在 *alarm_config.h* 中配置临时使用的联系人信息。 ```{image} images/image_R1cKbrB2Oomvybxf9m5cnB8fnag.webp :width: 663px :height: 140px :align: center ``` ### 构建项目 在开启的PowerShell窗口继续执行环境初始化命令: ```powershell unirtos-cli env-setup ``` 在PowerShell窗口执行固件编译命令(如使用模块型号非EG800ZCN_LH,请替换实际需要编译的型号): ```powershell unirtos-cli build -m EG800ZCN_LH -v EG800ZCN_LH_BETA_20260904 ``` 等待编译结束后,PowerShell窗口末尾会提示固件编译结果: ```plaintext SUCCESS: Unirtos project built successfully! ``` 固件编译成功后通过QFlash烧录到开发板中。 ### 板上验证 1. 长按开发板电源键(S1)开机,打开EPAT工具,根据 [日志调试教程]() 进行配置,配置完成后重启开发板,从模块上电开始抓取模块日志,确保日志输出完整。 ```{image} images/image_STTobLkkXoiqSpxfkUMcVSdan2e.webp :width: 1280px :height: 701px :align: center ``` 2. 在云平台下发实际需要的JSON格式的联系人信息,**重启开发板以应用新的联系人信息**。格式参考: ```json {"contacts":[{"name":"NAME1","phone":"135xxxxxxxx"},{"name":"NAME2","phone":"132xxxxxxxxx"}]} ``` ```{image} images/image_Lg36bZdPBo2x2qxEApEcofyJnib.webp :width: 739px :height: 239px :align: center ``` 3. 按下按键或语音唤醒(唤醒词默认为“救命”)触发报警,可组合多种情况进行测试,如第一联系人接通/失败,第二联系人接通/失败等,测试完成后,在EPAT搜索“[alarm]”查看运行日志。 ```{image} images/image_XdFabgbpDoEpm4x7VzVcH7Xtnyd.webp :width: 1280px :height: 569px :align: center ``` ## 效果说明 项目在三种状态下的现象如下: - 空闲: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持久化。 ## 业务流程 ```{image} images/board_Ze4TwW80RhK2lKbfLtrctKRznKe.jpg :width: 820px :height: 549px :align: center ``` ## 启动流程 ```{figure} images/board_CgEfwGqXChANIObsG3fcIeX4ncd.jpg :align: center :alt: image ``` **启动流程说明** 系统启动后,应用入口函数被自动注册并执行。*alarm_init()* 负责创建主任务和事件消息队列。*alarm_task()* 先初始化状态机、反馈层与外设服务,再进入循环。在循环中,程序持续等待事件;若有事件,则交给状态机处理;若没有事件,则执行周期tick。这样既能及时响应触发事件,也不会耗尽CPU资源。 ## 代码讲解 ### 项目结构 ```plaintext 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()* 创建报警应用任务和事件队列。 ```c 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语音识别共同构成,最终都会统一转成事件投递到状态机中。 ```c 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); } ``` ```c 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()*。 ```c 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); } ``` ```c 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接口发起呼叫和发送短信。 ```c 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; } ``` ```c 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语音播报 - 低电量轮询与告警 ```c 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); } ``` ```c 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格式示例: ```json //下发示例 { "contacts": [ {"name": "NAME1", "phone": "138xxxxxxxx"}, {"name": "NAME2", "phone": "138xxxxxxxx"}] } //回执示例 {"result":"OK","count":2,"persisted":1} ``` 云端下发的报文先经过解析校验,任一条目非法就整包拒绝,不会写入脏数据。 ```c 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); } ``` 联系人存储采用双缓冲:写方先填非活动缓冲,填完再翻转下标,读方无锁读取活动缓冲,避免拨号过程中联系人表被半途改写。 ```c 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; } ``` 状态机收到配置更新事件后,只在空闲态才刷新会话联系人数,报警进行中则推迟到下一次生效。 # 应用演示 第一次运行请先修改代码中的联系人信息,重新编译烧录新固件。或在代码中配置云平台信息,通过云平台下发联系人信息进行更新。 ## 报警触发 通过按键或语音成功触发后,此时指示灯将快速闪烁,表示进入报警状态。 ```{image} images/image_QvZ9bV1XuoMM4nxMo0ycD7PanDh.webp :width: 744px :height: 467px :align: center ``` ## 群发短信 报警触发后,程序将发送预设好的短信内容(可在代码中修改为自定义内容)通知所有联系人。 ```{image} images/image_N7LybPBiLoqJG1xVxHpc3pUbnQh.webp :width: 421px :height: 935px :align: center ``` ## 电话呼叫 群发短信后,程序将按照预设的联系人顺序,依次对各个联系人进行电话呼叫。 ```{image} images/image_G5cgblbo1oopkYxZBD2cOMlpnrb.webp :width: 392px :height: 856px :align: center ```