Files
stc32g128k/App/app_manager.c

962 lines
37 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "app_manager.h"
#include "rgb.h"
#include "event.h"
#include "database.h"
#include "ui.h"
#include "system.h"
#include "../Drivers/rf.h"
#include "../Drivers/lcd.h"
// 定义单个应用轮询 onRun 的最大允许 CPU 占用 Tick 数 (5 毫秒)
#define APP_ONRUN_MAX_TICKS 5
// 定义局部静态应用实例注册表,大小为 5 (即 APP_ID_MAX)
static WristbandApp app_registry[APP_ID_MAX];
// 指向当前活跃的前台应用程序的指针
static WristbandApp *active_app = NULL;
// 默认启动加载的应用:待机时钟页
#define DEFAULT_APP_ID APP_ID_CLOCK
// 二级传感器对码菜单中,各类传感器的法语显示名称定义
char *code menu_items[5] = {
"DETEC. PORTE", // 0: 门磁探测器 (法语)
"DETEC. PIR", // 1: 红外探测器
"DETEC. FUMEE", // 2: 烟雾传感器
"DETEC. GAZ", // 3: 燃气探测器
"DETEC. EAU" // 4: 水浸探测器
};
/* ====== 内部应用程序回调函数的前置声明 ====== */
// ClockApp (待机时钟应用) 生命周期函数
void ClockApp_OnStart(void);
void ClockApp_onRun(void);
void ClockApp_onClose(void);
EventResult ClockApp_onEvent(SystemEvent *evt);
// MenuApp (菜单选择应用) 生命周期函数
void MenuApp_OnStart(void);
void MenuApp_onRun(void);
void MenuApp_onClose(void);
EventResult MenuApp_onEvent(SystemEvent *evt);
// PairApp (无线传感器对码学习应用) 生命周期函数
void PairApp_OnStart(void);
void PairApp_onRun(void);
void PairApp_onClose(void);
EventResult PairApp_onEvent(SystemEvent *evt);
// AlarmApp (传感器报警显示与振动反馈应用) 生命周期函数
void AlarmApp_OnStart(void);
void AlarmApp_onRun(void);
void AlarmApp_onClose(void);
EventResult AlarmApp_onEvent(SystemEvent *evt);
// SOSApp (SOS 紧急求救无线发射与警灯应用) 生命周期函数
void SOSApp_OnStart(void);
void SOSApp_onRun(void);
void SOSApp_onClose(void);
EventResult SOSApp_onEvent(SystemEvent *evt);
/* ====== AppManager 核心接口函数实现 ====== */
/**
* @brief 初始化应用管理器并注册所有系统支持的应用
*/
void AppManager_Init(void)
{
u8 i;
/* 1. 清空初始化注册表 */
for (i = 0; i < APP_ID_MAX; i++) {
app_registry[i].app_id = APP_ID_MAX;
app_registry[i].app_type = APP_TYPE_FOREGROUND;
app_registry[i].app_name = "";
app_registry[i].OnStart = NULL;
app_registry[i].onRun = NULL;
app_registry[i].onClose = NULL;
app_registry[i].onEvent = NULL;
app_registry[i].is_active = 0;
app_registry[i].is_running = 0;
}
/* 2. 注册待机时钟应用 ClockApp */
app_registry[APP_ID_CLOCK].app_id = APP_ID_CLOCK;
app_registry[APP_ID_CLOCK].app_type = APP_TYPE_FOREGROUND;
app_registry[APP_ID_CLOCK].app_name = "Clock";
app_registry[APP_ID_CLOCK].OnStart = ClockApp_OnStart;
app_registry[APP_ID_CLOCK].onRun = ClockApp_onRun;
app_registry[APP_ID_CLOCK].onClose = ClockApp_onClose;
app_registry[APP_ID_CLOCK].onEvent = ClockApp_onEvent;
/* 3. 注册系统设置主菜单应用 MenuApp */
app_registry[APP_ID_MENU].app_id = APP_ID_MENU;
app_registry[APP_ID_MENU].app_type = APP_TYPE_FOREGROUND;
app_registry[APP_ID_MENU].app_name = "Menu";
app_registry[APP_ID_MENU].OnStart = MenuApp_OnStart;
app_registry[APP_ID_MENU].onRun = MenuApp_onRun;
app_registry[APP_ID_MENU].onClose = MenuApp_onClose;
app_registry[APP_ID_MENU].onEvent = MenuApp_onEvent;
/* 4. 注册无线对码配对应用 PairApp */
app_registry[APP_ID_PAIR].app_id = APP_ID_PAIR;
app_registry[APP_ID_PAIR].app_type = APP_TYPE_FOREGROUND;
app_registry[APP_ID_PAIR].app_name = "Pair";
app_registry[APP_ID_PAIR].OnStart = PairApp_OnStart;
app_registry[APP_ID_PAIR].onRun = PairApp_onRun;
app_registry[APP_ID_PAIR].onClose = PairApp_onClose;
app_registry[APP_ID_PAIR].onEvent = PairApp_onEvent;
/* 5. 注册防区入侵警报应用 AlarmApp */
app_registry[APP_ID_ALARM].app_id = APP_ID_ALARM;
app_registry[APP_ID_ALARM].app_type = APP_TYPE_FOREGROUND;
app_registry[APP_ID_ALARM].app_name = "Alarm";
app_registry[APP_ID_ALARM].OnStart = AlarmApp_OnStart;
app_registry[APP_ID_ALARM].onRun = AlarmApp_onRun;
app_registry[APP_ID_ALARM].onClose = AlarmApp_onClose;
app_registry[APP_ID_ALARM].onEvent = AlarmApp_onEvent;
/* 6. 注册 SOS 紧急求救应用 SOSApp */
app_registry[APP_ID_SOS].app_id = APP_ID_SOS;
app_registry[APP_ID_SOS].app_type = APP_TYPE_FOREGROUND;
app_registry[APP_ID_SOS].app_name = "SOS";
app_registry[APP_ID_SOS].OnStart = SOSApp_OnStart;
app_registry[APP_ID_SOS].onRun = SOSApp_onRun;
app_registry[APP_ID_SOS].onClose = SOSApp_onClose;
app_registry[APP_ID_SOS].onEvent = SOSApp_onEvent;
/* 默认运行启动待机时钟应用 */
AppManager_StartApp(DEFAULT_APP_ID);
}
/**
* @brief 切换并启动目标前台应用程序 (切应用生命周期)
* @param app_id 目标加载的应用 ID
*/
void AppManager_StartApp(AppID app_id)
{
WristbandApp *target;
if (app_id >= APP_ID_MAX) return; // 越界防护
target = &app_registry[app_id];
if (target->OnStart == NULL) return; // 空指针防护
if (active_app == target) return; // 避免重复启动同一个应用造成页面重画
Uart_SendString("[APP] StartApp: ");
Uart_SendString(target->app_name);
Uart_SendString("\r\n");
/* 1. 关闭清除当前活跃运行的应用,释放其外设和标志 */
if (active_app != NULL && active_app->onClose != NULL) {
active_app->is_active = 0; // 标明不再处于前台活跃状态
active_app->onClose(); // 调用该应用的退出现场清理回调
}
/* 2. 切换当前前台活跃应用指针,启动新加载的应用 */
active_app = target;
if (active_app->OnStart != NULL) {
active_app->is_active = 1; // 标明进入活跃状态
active_app->is_running = 1; // 标记处于运行状态,开启 onRun 调用
active_app->OnStart(); // 调用启动回调,初始化显示 UI 与外设
}
}
/**
* @brief 将捕获的系统事件分发给当前活跃的前台应用
* @param evt 待分发的系统事件
*/
void AppManager_DispatchEvent(SystemEvent evt)
{
EventResult result = EVENT_IGNORED;
if (active_app != NULL && active_app->is_active && active_app->onEvent != NULL) {
// 调用活跃应用的事件处理函数
result = active_app->onEvent(&evt);
if (result == EVENT_HANDLED) return; // 被前台消耗掉,结束分发
}
}
/**
* @brief 在主循环中持续轮询当前活跃应用的 onRun
* @details 包含 CPU 占用时间检测算法,避免坏设计的 onRun 回调死循环卡死系统。
*/
void AppManager_RunActiveApp(void)
{
u16 start_tick;
u16 elapsed;
if (active_app == NULL || !active_app->is_active) return;
// CPU 挂起恢复机制:如果在上一次循环中因超时被强行挂起 (is_running=0)
// 则在本次循环中将 is_running 标志重新置 1 恢复,但直接返回,跳过本次执行,把 CPU 使用权让给其他底层服务
if (!active_app->is_running) {
active_app->is_running = 1;
return;
}
if (active_app->onRun == NULL) return;
start_tick = ms_tick; // 记录开始前的毫秒 Tick 数 (0~999 循环)
active_app->onRun(); // 执行应用的轮询代码
// 计算本次 onRun 调用的真实耗时 (考虑 ms_tick 模 1000 归零的情况)
if (ms_tick >= start_tick) {
elapsed = ms_tick - start_tick;
} else {
elapsed = (1000 + ms_tick) - start_tick;
}
// 超时检测:如果单次 onRun 运行时间超过了 APP_ONRUN_MAX_TICKS (5毫秒)
if (elapsed > APP_ONRUN_MAX_TICKS) {
active_app->is_running = 0; // 强行挂起,下次循环跳过该应用 onRun 以释放 CPU
} else {
active_app->is_running = 1; // 正常,继续运行
}
}
/**
* @brief 获取当前正在活跃的前台应用 ID
*/
AppID AppManager_GetActiveAppID(void)
{
if (active_app == NULL) return APP_ID_MAX;
return active_app->app_id;
}
/* =========================================================================
* 1. ClockApp (待机时钟应用) 具体生命周期回调实现
* ========================================================================= */
// 编辑时间参数所用到的私有局部变量
static u8 time_edit_active = 0; // 标识当前是否正在编辑修改时间中 (1:是, 0:否)
static u8 time_edit_field = 1; // 标识当前聚焦在哪个参数上进行微调 (1:修改小时, 2:修改分钟)
static u8 time_edit_blink_on = 1; // 时钟修改时光标闪烁周期状态位
/**
* @brief 触发启动时间设置状态机,准备修改时钟
*/
void Start_Time_Edit(void)
{
time_edit_active = 1;
time_edit_field = 1;
time_edit_blink_on = 1;
current_state = STATE_SET_TIME; // 系统切入时间设置状态
// FCOB 幻彩灯条双灯珠均显示蓝色常亮 (R=0, G=0, B=180),进行模式视觉区分
RGB_Send(0, 0, 180, 0, 0, 180);
// 振动马达单次短震 50 毫秒,提供触觉反馈
MOTOR = 1;
Delay_ms(50);
MOTOR = 0;
// 渲染显示时间编辑界面,默认高亮编辑小时字段,消隐位开启
UI_ShowSetTimePage(current_hour, current_min, time_edit_field, 1);
}
/**
* @brief ClockApp 启动回调
*/
void ClockApp_OnStart(void)
{
time_edit_active = 0;
current_state = STATE_NORMAL; // 系统切入正常待机状态
RF_SetMode(1); // 开启天线并置为接收模式,监听无线探测器
UI_ShowClockPage(current_state, current_hour, current_min); // 渲染时钟主页
// 关断/熄灭 FCOB 双灯条
RGB_Send(0, 0, 0, 0, 0, 0);
Uart_SendString("[ClockApp] Started\r\n");
}
/**
* @brief ClockApp 轮询主回调
* @details 负责处理两个非阻塞式周期性事务:
* 1. 若正在时间修改编辑模式中,基于 ms_tick 提供 500ms 周期的被改参数隐现闪烁效果。
* 2. 在待机主页常态下,非阻塞轮询射频解调接收缓冲区,识别到无线信号后在数据库中匹配,并实现防区布撤防规则过滤。
*/
void ClockApp_onRun(void)
{
u32 rx_addr;
u8 rx_data;
if (time_edit_active)
{
// ====== 时间设置闪烁动画 (周期 500ms) ======
u8 blink = (ms_tick / 500) % 2;
if (blink != time_edit_blink_on)
{
time_edit_blink_on = blink;
// 每次闪烁状态发生变化,重新刷写屏幕数值
UI_ShowSetTimePage(current_hour, current_min, time_edit_field, time_edit_blink_on);
}
}
else
{
// ====== 常态待机下的射频检测与触发过滤逻辑 ======
// 软时钟发生分钟改变,需要通知屏幕重画时钟主页
if (clock_updated) {
clock_updated = 0;
UI_ShowClockPage(current_state, current_hour, current_min);
}
// 轮询无线 EV1527 硬件解调器是否收到一个完整的 24 位无线码
if (EV1527_Decode(&rx_addr, &rx_data)) {
u8 slot;
// 比对收到的 24 位射频源地址是否合法注册在手环本地配置数据库中
if (Check_Sensor_ID(rx_addr, &slot)) {
u8 sensor_type = sensor_list[slot].type; // 获取注册的设备类型
if (sensor_type < 5) {
// 【防区分类过滤与布撤防过滤逻辑】:
// - 0号防区或者烟感(2)、气感(3)、水浸(4) 为 24小时紧急防区不受当前布防状态影响一律强制触发警报
// - 门磁(0)和红外(1)为普通边界入侵防区,只有当系统处于 STATE_ARMED (布防) 状态时,才触发入侵警报。
if (sensor_list[slot].zone == 0 ||
sensor_type == 2 ||
sensor_type == 3 ||
sensor_type == 4 ||
current_state == STATE_ARMED)
{
SystemEvent rf_evt;
rf_evt.key_event = KEY_EVENT_NONE;
rf_evt.priority = EVENT_PRIORITY_HIGH; // 将事件定为 HIGH (高优先级)
rf_evt.extra_data = rx_addr; // 携带报警源地址
rf_evt.extra_size = 4;
// 高优先级事件,强行插队到队首优先执行以提高响应速率
EventQueue_InsertFront(rf_evt);
}
}
}
}
}
}
/**
* @brief ClockApp 退出清理回调
*/
void ClockApp_onClose(void)
{
time_edit_active = 0;
RGB_Send(0, 0, 0, 0, 0, 0); // 关闭灯条
Uart_SendString("[ClockApp] Closed\r\n");
}
/**
* @brief ClockApp 接收事件处理回调 (按键导航与布撤防交互)
*/
EventResult ClockApp_onEvent(SystemEvent *evt)
{
if (time_edit_active)
{
// ====== 处于时间设置状态下的按键拦截处理机 ======
if (evt->key_event == KEY_EVENT_UP_CLICK)
{
// 短按 ▲ 键增加数值 (小时/分钟循环相加)
if (time_edit_field == 1) {
current_hour = (current_hour + 1) % 24;
} else {
current_min = (current_min + 1) % 60;
}
UI_ShowSetTimePage(current_hour, current_min, time_edit_field, 1);
return EVENT_HANDLED; // 吞掉并消化事件
}
if (evt->key_event == KEY_EVENT_DOWN_CLICK)
{
// 短按 ▼ 键减小数值
if (time_edit_field == 1) {
current_hour = (current_hour == 0) ? 23 : (current_hour - 1);
} else {
current_min = (current_min == 0) ? 59 : (current_min - 1);
}
UI_ShowSetTimePage(current_hour, current_min, time_edit_field, 1);
return EVENT_HANDLED;
}
if (evt->key_event == KEY_EVENT_CONFIRM_LONG)
{
// 长按 ■ 确认键切换调节字段 (在修改小时与修改分钟间跳转)
if (time_edit_field == 1) {
time_edit_field = 2;
} else {
time_edit_field = 1;
}
UI_ShowSetTimePage(current_hour, current_min, time_edit_field, 1);
return EVENT_HANDLED;
}
if (evt->key_event == KEY_EVENT_CONFIRM_CLICK)
{
// 短按 ■ 确认键保存最新时钟配置并退出编辑模式
time_edit_active = 0;
current_state = STATE_NORMAL;
RGB_Send(0, 0, 0, 0, 0, 0); // 熄灭蓝色指示灯
// 退出短振反馈
MOTOR = 1; Delay_ms(50); MOTOR = 0;
UI_ShowClockPage(current_state, current_hour, current_min);
return EVENT_HANDLED;
}
return EVENT_HANDLED; // 在时间设置模式下屏蔽拦截其他一切无关按键事件
}
// ====== 正常时钟对待机状态下的按键拦截处理 ======
// 1. 长按 ■ 确认键,打开设置主菜单 MenuApp
if (evt->key_event == KEY_EVENT_CONFIRM_LONG) {
AppManager_StartApp(APP_ID_MENU);
return EVENT_HANDLED;
}
// 2. 长按 ▲ 键,一键切换系统的 布防 (ARMED) 和 撤防 (DISARMED) 状态
if (evt->key_event == KEY_EVENT_UP_LONG) {
if (current_state == STATE_ARMED) {
current_state = STATE_DISARMED;
} else {
current_state = STATE_ARMED;
}
// 布撤防状态切换成功后进行 100ms 短震确认,并重新刷写时钟待机页
MOTOR = 1; Delay_ms(100); MOTOR = 0;
UI_ShowClockPage(current_state, current_hour, current_min);
return EVENT_HANDLED;
}
// 3. 遇到 SOS 物理求救按键(无论短按还是长按),直接进入 SOSAPP
if (evt->key_event == KEY_EVENT_SOS_CLICK || evt->key_event == KEY_EVENT_SOS_LONG) {
AppManager_StartApp(APP_ID_SOS);
return EVENT_HANDLED;
}
return EVENT_IGNORED; // 其他未被关心的事件交由后台应用或丢弃
}
/* =========================================================================
* 2. MenuApp (菜单选择应用) 具体生命周期回调实现
* ========================================================================= */
// menu_level: 0-主菜单, 1-选择传感器二级对码菜单, 2-网络摄像机(IPC)一键绑定发射中
static u8 menu_level = 0;
// 记录上一次进行 IPC 绑定无线射频发射的时间 Tick 数,实现周期性发射
static u16 last_bind_tx_ms = 0;
/**
* @brief MenuApp 启动回调
*/
void MenuApp_OnStart(void)
{
menu_level = 0;
menu_select = 0;
current_state = STATE_PAIR_MENU; // 系统运行状态设为对码配置菜单
RF_SetMode(0); // 关闭射频接收芯片,防止接收杂波干扰屏幕绘制
UI_ShowMainMenu(menu_select); // 初始化绘制一级主菜单
RGB_Send(0, 0, 0, 0, 0, 0); // 熄灭所有灯条
Uart_SendString("[MenuApp] Started (Main Menu)\r\n");
}
/**
* @brief MenuApp 轮询主回调
* @details 当且仅当进入 IPC 绑定状态下 (menu_level == 2) 时运行。
* - 在 FCOB 灯条上产生青色 (R=0, G=val, B=val) 的非阻塞呼吸效果以暗示绑定进程。
* - 控制无线发射引脚,每隔 500ms 向外广播一次克隆的 EV1527 24位出厂物理 ID 的绑定对码帧。
*/
void MenuApp_onRun(void)
{
if (menu_level == 2)
{
// ====== 控制青色双灯珠同步呼吸效果 (呼吸周期 1000ms) ======
u16 breath = ms_tick % 1000;
u8 val = (breath < 500) ? (breath * 2 / 5) : ((1000 - breath) * 2 / 5); // 将 0~500 范围比例映射为 0~200 亮度
RGB_Send(0, val, val, 0, val, val); // 渲染青色 (G+B)
// ====== 控制无线发射,每 500ms 广播一次配对数据 ======
if (ms_tick - last_bind_tx_ms >= 500 || ms_tick < last_bind_tx_ms)
{
last_bind_tx_ms = ms_tick; // 备份当前 Tick 时间
RF_SetMode(2); // 暂时切换射频芯片为发射调制模式
EV1527_Transmit(CLONED_ADDR, 0x01); // 调制发射手环的 24 位物理 ID + 绑定特定数据码 (0x01)
RF_SetMode(0); // 切回关闭/高阻状态,避免噪声产生
}
}
}
/**
* @brief MenuApp 退出清理回调
*/
void MenuApp_onClose(void)
{
menu_level = 0;
RGB_Send(0, 0, 0, 0, 0, 0); // 熄灭 RGB
RF_SetMode(1); // 重新开启无线接收模式
Uart_SendString("[MenuApp] Closed\r\n");
}
/**
* @brief MenuApp 接收按键与控制事件回调
*/
EventResult MenuApp_onEvent(SystemEvent *evt)
{
if (menu_level == 0)
{
// ====== 处于一级主菜单层 (0-HORLOGE, 1-APPAIRAGE, 2-LIAISON) ======
if (evt->key_event == KEY_EVENT_UP_CLICK) {
// 短按 ▲ 向上移动选中焦点
if (menu_select > 0) {
menu_select--;
UI_ShowMainMenu(menu_select); // 重新画屏
}
return EVENT_HANDLED;
}
if (evt->key_event == KEY_EVENT_DOWN_CLICK) {
// 短按 ▼ 向下移动选中焦点
if (menu_select < 2) {
menu_select++;
UI_ShowMainMenu(menu_select);
}
return EVENT_HANDLED;
}
if (evt->key_event == KEY_EVENT_CONFIRM_CLICK) {
// 短按 ■ 确认键,进入选定功能的设置页面或二级菜单
if (menu_select == 0) {
// 功能 1设置时间。启动待机时钟应用并调用微调编辑入口
AppManager_StartApp(APP_ID_CLOCK);
Start_Time_Edit();
} else if (menu_select == 1) {
// 功能 2传感器学习。进入二级传感器类别选择菜单
menu_level = 1;
menu_select = 0;
UI_ShowPairMenuPage(menu_select); // 绘制二级设备分类页
} else if (menu_select == 2) {
// 功能 3摄像机配对。进入摄像机绑定状态
menu_level = 2;
last_bind_tx_ms = ms_tick; // 记录初始发射时间
current_state = STATE_IPC_BIND; // 系统设为 IPC 绑定状态
UI_ShowBindingPage(CLONED_ADDR); // 绘制摄像机绑定信息页
}
return EVENT_HANDLED;
}
if (evt->key_event == KEY_EVENT_CONFIRM_LONG) {
// 长按 ■ 确认键,退出设置菜单直接返还待机时钟
AppManager_StartApp(APP_ID_CLOCK);
return EVENT_HANDLED;
}
}
else if (menu_level == 1)
{
// ====== 处于二级传感器对码类型菜单层 (0-门磁, 1-PIR, 2-烟感, 3-气感, 4-水浸) ======
if (evt->key_event == KEY_EVENT_UP_CLICK) {
// 上移焦点
if (menu_select > 0) {
menu_select--;
UI_ShowPairMenuPage(menu_select);
}
return EVENT_HANDLED;
}
if (evt->key_event == KEY_EVENT_DOWN_CLICK) {
// 下移焦点
if (menu_select < 4) {
menu_select++;
UI_ShowPairMenuPage(menu_select);
}
return EVENT_HANDLED;
}
if (evt->key_event == KEY_EVENT_CONFIRM_CLICK) {
// 确认选择:缓存用户选好的待绑定硬件类型,短震 100ms 反馈,并加载对码学习应用 (PairApp)
captured_type = menu_select;
MOTOR = 1;
Delay_ms(100);
MOTOR = 0;
AppManager_StartApp(APP_ID_PAIR); // 跳转加载 PairApp
return EVENT_HANDLED;
}
if (evt->key_event == KEY_EVENT_DOWN_LONG) {
// 长按 ▼ 键,从二级菜单退回到一级主菜单
menu_level = 0;
menu_select = 1; // 回到 APPAIRAGE 选项上
UI_ShowMainMenu(menu_select);
return EVENT_HANDLED;
}
}
else if (menu_level == 2)
{
// ====== 处于摄像机配对一键绑定发送层 ======
// 长按 ■ 确认键 (3秒) 或 长按 ▼ 键 (2秒) 均表示取消绑定,退出发射并返回一级主菜单
if (evt->key_event == KEY_EVENT_CONFIRM_LONG || evt->key_event == KEY_EVENT_DOWN_LONG) {
menu_level = 0;
menu_select = 2; // 回到 LIAISON 选项上
current_state = STATE_PAIR_MENU;
RGB_Send(0, 0, 0, 0, 0, 0); // 关闭呼吸灯
UI_ShowMainMenu(menu_select);
return EVENT_HANDLED;
}
}
return EVENT_IGNORED;
}
/* =========================================================================
* 3. PairApp (无线传感器对码学习应用) 具体生命周期回调实现
* ========================================================================= */
// PairApp 私有状态状态机机定义
enum {
PAIR_STATE_WAIT, // 等待信号输入 (雷达搜寻画面)
PAIR_STATE_CONFIRM, // 已捕获,防区选择与微调确认画面
PAIR_STATE_SUCCESS, // 绑定成功,显示 SUCCESS 对勾页
PAIR_STATE_FAIL // 超时或失败,显示 FAIL 叉号页
};
static u8 pair_state; // 当前的对码阶段状态
static u8 last_radar_sec; // 上一次雷达页面帧索引备份
static u8 captured_zone = 1; // 当前配置准备分配给该传感器的防区号 (1~99)
static u8 success_start_sec = 0; // 记录对码成功时的开始秒数,以便延迟退出
static u8 fail_start_sec = 0; // 记录对码失败时的开始秒数
/**
* @brief PairApp 启动回调
*/
void PairApp_OnStart(void)
{
pair_state = PAIR_STATE_WAIT; // 初始化设为等待无线探测器状态
last_radar_sec = 99; // 强制刷写
current_state = STATE_PAIR_WAIT;
pair_timeout_ms = 0; // 对码超时毫秒计时清零
captured_zone = 1; // 默认防区分配号为 1
RF_SetMode(1); // 开启无线接收
// 初始化对码搜索页面绘制
UI_ShowPairWaitPage(0);
// 开启白色 RGB 幻彩双灯珠常亮 (R=150, G=150, B=150),作为对码标志
RGB_Send(150, 150, 150, 150, 150, 150);
Uart_SendString("[PairApp] Started (WAIT)\r\n");
}
/**
* @brief PairApp 轮询主回调
* @details 非阻塞式控制以下配对时序:
* 1. 处于 PAIR_STATE_WAIT 状态:控制双灯进行白色 3Hz 高频闪烁,控制雷达动画绘制,并进行 30 秒超时判定。
* 同时监测射频接收,收到无线码后,排除 SOS (0x08) 码,将系统切入防区确认状态。
* 2. 处于 PAIR_STATE_CONFIRM 状态:控制灯光橙色慢闪。
* 3. 处于 SUCCESS / FAIL 状态:控制延迟 2秒 或 5秒 自动退出,重新回到二级对码菜单页。
*/
void PairApp_onRun(void)
{
u32 rx_addr;
u8 rx_data;
u8 flash;
u8 frame;
if (pair_state == PAIR_STATE_WAIT) {
// ====== 等待状态:白色高频快闪 (3Hz周期 333ms) ======
flash = (ms_tick / 166) % 2;
if (flash) RGB_Send(150, 150, 150, 150, 150, 150);
else RGB_Send(0, 0, 0, 0, 0, 0);
// 每隔 250ms滚动绘制雷达搜索的 3 帧图片,构成扩散环形雷达图
frame = (u8)((ms_tick / 250) % 3);
if (frame != last_radar_sec) {
last_radar_sec = frame;
UI_ShowPairWaitPage(frame);
}
// ====== 30 秒超时判定 ======
if (pair_timeout_ms >= 30000UL) {
pair_timeout_ms = 0;
pair_state = PAIR_STATE_FAIL; // 切入失败
current_state = STATE_PAIR_FAIL;
fail_start_sec = current_sec; // 记录失败时间
UI_ShowPairFailPage(1); // 绘制超时失败大红叉页
// 红色双灯常亮警告
RGB_Send(200, 0, 0, 200, 0, 0);
Uart_SendString("[PairApp] Timeout\r\n");
return;
}
// ====== 射频包监听接收 ======
if (EV1527_Decode(&rx_addr, &rx_data)) {
// 【保护机制】:排除 SOS 数据码 (0x08)。SOS 主动求救的数据格式不能作为普通防区进行学习绑定。
if (rx_data != 0x08)
{
captured_addr = rx_addr; // 锁定射频源地址
pair_state = PAIR_STATE_CONFIRM; // 进入防区微调确认模式
current_state = STATE_PAIR_CONFIRM;
RF_SetMode(0); // 关闭射频接收,防止再次覆盖
captured_zone = 1; // 默认初始防区为 1
UI_ShowPairConfirmPage(captured_type, captured_zone); // 刷新界面
Uart_SendString("[PairApp] Sensor captured\r\n");
}
}
} else if (pair_state == PAIR_STATE_CONFIRM) {
// ====== 确认状态:灯光橙色慢闪 (1.5Hz,周期 666ms) ======
u8 flash = (ms_tick / 333) % 2;
if (flash) RGB_Send(100, 180, 0, 100, 180, 0); // 橙色 (G=180, R=100)
else RGB_Send(0, 0, 0, 0, 0, 0);
} else if (pair_state == PAIR_STATE_SUCCESS) {
// ====== 成功状态:延时 2 秒自动返回二级设置菜单 ======
u8 elapsed = (current_sec >= success_start_sec) ? (current_sec - success_start_sec) : (60 + current_sec - success_start_sec);
if (elapsed >= 2)
{
AppManager_StartApp(APP_ID_MENU);
}
} else if (pair_state == PAIR_STATE_FAIL) {
// ====== 失败状态:延时 5 秒自动返回二级设置菜单 ======
u8 elapsed = (current_sec >= fail_start_sec) ? (current_sec - fail_start_sec) : (60 + current_sec - fail_start_sec);
if (elapsed >= 5)
{
AppManager_StartApp(APP_ID_MENU);
}
}
}
/**
* @brief PairApp 退出清理
*/
void PairApp_onClose(void)
{
RGB_Send(0, 0, 0, 0, 0, 0); // 关灯
MOTOR = 0; // 停震
RF_SetMode(1); // 重开射频接收
Uart_SendString("[PairApp] Closed\r\n");
}
/**
* @brief PairApp 接收事件处理 (防区微调与对码保存)
*/
EventResult PairApp_onEvent(SystemEvent *evt)
{
if (pair_state == PAIR_STATE_CONFIRM) {
if (evt->key_event == KEY_EVENT_UP_CLICK) {
// 短按 ▲ 键增加目标防区号 (最大 99)
if (captured_zone < 99) {
captured_zone++;
UI_ShowPairConfirmPage(captured_type, captured_zone);
}
return EVENT_HANDLED;
}
if (evt->key_event == KEY_EVENT_DOWN_CLICK) {
// 短按 ▼ 键减小目标防区号 (最小 1)
if (captured_zone > 1) {
captured_zone--;
UI_ShowPairConfirmPage(captured_type, captured_zone);
}
return EVENT_HANDLED;
}
if (evt->key_event == KEY_EVENT_CONFIRM_CLICK) {
// 短按 ■ 确认键,写入配置数据库并启动保存
Add_Sensor_With_Zone(captured_addr, captured_type, captured_zone);
pair_state = PAIR_STATE_SUCCESS; // 切入成功反馈状态
current_state = STATE_PAIR_SUCCESS;
success_start_sec = current_sec; // 记录开始秒数
UI_ShowPairSuccessPage(); // 绘制成功画面
// 绿色双灯常亮,并开启振动马达长震 400ms暗示配对保存圆满完成
RGB_Send(0, 200, 0, 0, 200, 0);
MOTOR = 1;
Delay_ms(400);
MOTOR = 0;
return EVENT_HANDLED;
}
if (evt->key_event == KEY_EVENT_CONFIRM_LONG) {
// 长按 ■ 确认键,放弃本次捕获直接取消配对,回到主菜单
RGB_Send(0, 0, 0, 0, 0, 0);
AppManager_StartApp(APP_ID_MENU);
return EVENT_HANDLED;
}
}
// 在搜寻期或确认期,长按 ▼ 键皆可强制放弃并退回二级菜单
if (evt->key_event == KEY_EVENT_DOWN_LONG) {
RGB_Send(0, 0, 0, 0, 0, 0);
AppManager_StartApp(APP_ID_MENU);
return EVENT_HANDLED;
}
return EVENT_IGNORED;
}
/* =========================================================================
* 4. AlarmApp (传感器报警显示与振动反馈应用) 具体生命周期回调实现
* ========================================================================= */
/**
* @brief AlarmApp 启动回调
* @details 判定触发警报的传感器信息,初始化报警 UI输出对应的声光颜色。
*/
void AlarmApp_OnStart(void)
{
u8 slot;
u8 type = 0;
u8 zone = 1;
current_state = STATE_ALARM; // 系统切入报警状态
alarm_timer_ms = 0; // 振动波形状态机定时器清零
alarm_flash_flag = 0;
// 比对查出当前触发高优先级警报的传感器在数据库中的属性
if (Check_Sensor_ID(captured_addr, &slot)) {
type = sensor_list[slot].type;
zone = sensor_list[slot].zone;
alarm_sensor_slot = slot;
}
// 渲染显示报警图文界面
UI_ShowAlarmPage(type, zone);
// 首次亮起防区特定的警示色彩,起到区分报警硬件的目的:
if (type == 0) RGB_Send(0, 180, 0, 0, 180, 0); // 门磁: 红色 (GRB中R=180,G=0)
else if (type == 1) RGB_Send(100, 180, 0, 100, 180, 0); // 红外PIR: 黄色 (G=100, R=180)
else if (type == 2) RGB_Send(180, 0, 0, 180, 0, 0); // 烟感: 绿色 (G=180)
else if (type == 3) RGB_Send(0, 180, 180, 0, 180, 180); // 气感: 紫色 (R=180, B=180)
else RGB_Send(0, 0, 180, 0, 0, 180); // 水浸: 蓝色 (B=180)
Uart_SendString("[AlarmApp] Started\r\n");
}
/**
* @brief AlarmApp 轮询主回调
* @details 负责非阻塞式同步控制 RGB 爆闪,并向 OLED 屏幕的四周边框绘制或擦除红边框,形成像素爆闪动效。
* (马达具体的报警振动波形由于对毫秒时序要求极高,已交由 Timer1_Isr 进行高精度的实时管理控制)。
*/
void AlarmApp_onRun(void)
{
u8 type = sensor_list[alarm_sensor_slot].type;
u8 flash = (ms_tick / 250) % 2; // 以 250ms 为周期周期亮灭
// 1. 同步控制 RGB 双幻彩灯进行交替爆闪
if (flash)
{
if (type == 0) RGB_Send(0, 180, 0, 0, 180, 0);
else if (type == 1) RGB_Send(100, 180, 0, 100, 180, 0);
else if (type == 2) RGB_Send(180, 0, 0, 180, 0, 0);
else if (type == 3) RGB_Send(0, 180, 180, 0, 180, 180);
else RGB_Send(0, 0, 180, 0, 0, 180);
}
else
{
RGB_Send(0, 0, 0, 0, 0, 0); // 灭灯
}
// 2. 控制 Truly AMOLED 屏幕的最外围红色警告框闪烁效果
if (alarm_flash_flag) {
LCD_DrawRectBorder(4, 4, 112, 232, COLOR_RED);
LCD_DrawRectBorder(6, 6, 108, 228, COLOR_RED);
} else {
LCD_DrawRectBorder(4, 4, 112, 232, COLOR_BLACK);
LCD_DrawRectBorder(6, 6, 108, 228, COLOR_BLACK);
}
}
/**
* @brief AlarmApp 退出清理
*/
void AlarmApp_onClose(void)
{
RGB_Send(0, 0, 0, 0, 0, 0); // 关闭灯条
MOTOR = 0; // 停震
current_state = STATE_NORMAL;
Uart_SendString("[AlarmApp] Closed\r\n");
}
/**
* @brief AlarmApp 接收事件处理回调
* @details 报警发生时,用户按下手环任意键,均代表已阅读入侵警报,应用将结束报警并退回待机时钟。
*/
EventResult AlarmApp_onEvent(SystemEvent *evt)
{
if (evt->key_event != KEY_EVENT_NONE) {
AppManager_StartApp(APP_ID_CLOCK); // 清除报警并切回 Clock
return EVENT_HANDLED;
}
return EVENT_IGNORED;
}
/* =========================================================================
* 5. SOSApp (SOS 紧急求救无线发射与警灯应用) 具体生命周期回调实现
* ========================================================================= */
// 私有标志,用作记录 SOS 的发射半周期状态
static u8 sos_last_phase;
/**
* @brief SOSApp 启动回调
*/
void SOSApp_OnStart(void)
{
current_state = STATE_SOS_EMITTED; // 进入 SOS 紧急求救状态
sos_last_phase = 99;
alarm_timer_ms = 0; // 马达震动波形计数器清零
UI_ShowSosPage(); // 初始化渲染 SOS 警示主页
// 立即点亮左侧灯为红色,右侧熄灭,准备开启双色红灭交替爆闪
RGB_Send(0, 200, 0, 0, 0, 0);
Uart_SendString("[SOSApp] Started\r\n");
}
/**
* @brief SOSApp 轮询主回调
* @details 负责控制两个非阻塞的时序任务:
* 1. 控制无线发射芯片:每 500ms 开启发射模式,广播发射手环的 24 位出厂物理 Factory ID + SOS特定数据码 (0x08)。
* 2. 控制警示灯效:以 50ms 的超快频率让灯条上的两个灯珠进行左右交替红色爆闪,达到强烈警告的视觉效果。
* 同时同步闪烁屏幕边框。
*/
void SOSApp_onRun(void)
{
// ====== 1. 控制无线射频,每 500ms 发射一次 SOS 无线数据帧 (0x08) ======
u8 sec_phase = (u8)((ms_tick / 500) % 2);
if (sec_phase != sos_last_phase) {
sos_last_phase = sec_phase;
RF_SetMode(2); // 切换发射
EV1527_Transmit(CLONED_ADDR, 0x08); // 发射克隆 ID 及其 SOS (0x08) 命令字
RF_SetMode(0); // 释放接收
}
// ====== 2. 控制 RGB 双灯珠以 50ms 频率进行高频左右交替红色爆闪 ======
{
u8 flash = (ms_tick / 50) % 2;
if (flash) {
RGB_Send(0, 200, 0, 0, 0, 0); // 灯1亮红 (G=200,GRB中R排在第二)灯2灭
} else {
RGB_Send(0, 0, 0, 0, 200, 0); // 灯1灭灯2亮红
}
}
// ====== 3. 控制屏幕绘制多层爆闪红白框 ======
if (alarm_flash_flag) {
LCD_DrawRectBorder(4, 4, 112, 232, COLOR_RED);
LCD_DrawRectBorder(5, 5, 110, 230, COLOR_RED);
LCD_DrawRectBorder(8, 8, 104, 224, COLOR_WHITE);
} else {
LCD_DrawRectBorder(4, 4, 112, 232, COLOR_BLACK);
LCD_DrawRectBorder(5, 5, 110, 230, COLOR_BLACK);
LCD_DrawRectBorder(8, 8, 104, 224, COLOR_BLACK);
}
}
/**
* @brief SOSApp 退出清理
*/
void SOSApp_onClose(void)
{
RGB_Send(0, 0, 0, 0, 0, 0); // 关闭 RGB
MOTOR = 0; // 停震
current_state = STATE_NORMAL;
Uart_SendString("[SOSApp] Closed\r\n");
}
/**
* @brief SOSApp 接收事件处理
* @details 紧急呼救状态下,短按或长按物理按键,代表解除报警,直接切回待机时钟 ClockApp。
*/
EventResult SOSApp_onEvent(SystemEvent *evt)
{
if (evt->key_event != KEY_EVENT_NONE) {
AppManager_StartApp(APP_ID_CLOCK); // 退出 SOS回到时钟
return EVENT_HANDLED;
}
return EVENT_IGNORED;
}