紧急呼叫报警器¶
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驱动的蜂鸣器模块 |
快速上手¶
硬件连接¶
软件部署¶
开发环境搭建¶
参考 UniRTOS快速上手 文档,了解如何搭建开发环境并完成基本开发流程。
代码拉取¶
新开启一个PowerShell窗口,执行以下命令:
# 拉取示例仓库
unirtos-cli new -r unirtos-maker-examples
# 进入该项目
cd unirtos-maker-examples/emergency_call_alarm
修改配置参数¶
云平台配置参数
在 alarm_config.h 中配置云平台相关参数。关于阿里云平台产品创建与参数获取,可参考 AliYun平台 说明。
联系人信息配置
云平台未下发联系人信息时,可在 alarm_config.h 中配置临时使用的联系人信息。
构建项目¶
在开启的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烧录到开发板中。
板上验证¶
长按开发板电源键(S1)开机,打开EPAT工具,根据 日志调试教程 进行配置,配置完成后重启开发板,从模块上电开始抓取模块日志,确保日志输出完整。
在云平台下发实际需要的JSON格式的联系人信息,重启开发板以应用新的联系人信息。格式参考:
{"contacts":[{"name":"NAME1","phone":"135xxxxxxxx"},{"name":"NAME2","phone":"132xxxxxxxxx"}]}
按下按键或语音唤醒(唤醒词默认为“救命”)触发报警,可组合多种情况进行测试,如第一联系人接通/失败,第二联系人接通/失败等,测试完成后,在EPAT搜索“[alarm]”查看运行日志。
效果说明¶
项目在三种状态下的现象如下:
空闲:LED模块呼吸灯效果,蜂鸣器关闭。
拨号中:报警触发直至接通阶段,LED快速闪烁,蜂鸣器开启。
通话中:电话接通状态下,LED和蜂鸣器均关闭。
报警触发和所有联系人拨号失败两种情况下,会有TTS播报提醒。
软件设计详解¶
方案框架¶
软件划分为六层:
核心逻辑层:core.c 维护报警会话状态机,是业务决策中心。
中介模块:app.c 仅传递低频控制事件,不承载业务逻辑。
服务层:service/call_sms.c 提供IMS呼叫、短信群发和Codec链路准备能力。
反馈层:feedback/indicator.c 根据状态切换LED、蜂鸣器与TTS播报。
外设层:machine/key.c 与 asr_pro.c 把按键中断、语音关键词转换成统一事件。
云配置层:cloud/ 负责联系人下发、JSON解析与NV持久化。
业务流程¶
启动流程¶
启动流程说明
系统启动后,应用入口函数被自动注册并执行。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()。这个函数负责完成三件事:
设置GPIO电压;
打印启动日志;
调用 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;
}
状态机收到配置更新事件后,只在空闲态才刷新会话联系人数,报警进行中则推迟到下一次生效。
应用演示¶
第一次运行请先修改代码中的联系人信息,重新编译烧录新固件。或在代码中配置云平台信息,通过云平台下发联系人信息进行更新。
报警触发¶
通过按键或语音成功触发后,此时指示灯将快速闪烁,表示进入报警状态。
群发短信¶
报警触发后,程序将发送预设好的短信内容(可在代码中修改为自定义内容)通知所有联系人。
电话呼叫¶
群发短信后,程序将按照预设的联系人顺序,依次对各个联系人进行电话呼叫。