重构手环软件架构:实现动态挂载前后台 App 框架,解耦拆分源文件,并引入统一日志宏 xtell.h

This commit is contained in:
Antigravity IDE
2026-07-17 09:08:53 +08:00
parent 8fdbb32f42
commit c0d1fe74f5
18 changed files with 1380 additions and 1155 deletions

182
App/app_alarm.c Normal file
View File

@@ -0,0 +1,182 @@
#include "app_manager.h"
#include "xtell.h"
#include "ui.h"
#include "rgb.h"
#include "event.h"
#include "system.h"
#include "database.h"
#include "../Drivers/lcd.h"
// 外部全局变量引用
extern SystemState current_state;
extern u32 captured_addr;
extern u8 alarm_sensor_slot;
extern volatile u16 ms_tick;
extern volatile bit alarm_flash_flag;
// 传感器存储列表数据库声明
extern Sensor_Slot xdata sensor_list[16];
// 本地私有变量
static u32 alarm_timer_ms = 0; // 报警震动波形计数器
static u16 last_run_tick = 0; // 记录上一次轮询的 ticks
// 声明本应用的生命周期回调函数
static void AlarmApp_OnStart(void);
static void AlarmApp_onRun(void);
static void AlarmApp_onClose(void);
static EventResult AlarmApp_onEvent(SystemEvent *evt);
// 实体化定义警报应用控制块
WristbandApp alarm_app = {
APP_ID_ALARM,
"Alarm",
AlarmApp_OnStart,
AlarmApp_onRun,
AlarmApp_onClose,
AlarmApp_onEvent
};
/**
* @brief AlarmApp 启动回调
*/
static void AlarmApp_OnStart(void)
{
u8 slot;
u8 type = 0;
u8 zone = 1;
current_state = STATE_ALARM; // 系统切入报警状态
alarm_timer_ms = 0; // 振动计时器清零
last_run_tick = ms_tick;
// 比对查出当前触发警报的传感器在数据库中的属性
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)
XTELL_LOG("[AlarmApp] Started. Type=");
XTELL_LOG_HEX8(type);
XTELL_LOG("\r\n");
}
/**
* @brief AlarmApp 轮询主回调
* @details 包含同步的灯光爆闪和 Truly AMOLED 像素边框闪烁动效,
* 以及马达差异化震动波形逻辑的自处理。
*/
static void AlarmApp_onRun(void)
{
u8 type = sensor_list[alarm_sensor_slot].type;
u8 flash_light = (ms_tick / 250) % 2; // 250ms 周期亮灭
u16 tick_diff;
// 1. 同步控制 RGB 幻彩灯条进行交替爆闪
if (flash_light)
{
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);
}
// 3. 计算时间差并执行马达差异化震动状态机控制
if (ms_tick >= last_run_tick) {
tick_diff = ms_tick - last_run_tick;
} else {
tick_diff = 1000 + ms_tick - last_run_tick;
}
last_run_tick = ms_tick;
alarm_timer_ms += tick_diff;
if (alarm_timer_ms >= 5000) {
alarm_timer_ms = 0; // 5秒一周期
}
if (type == 0) {
// 门磁警报波形:单次震动 300ms停 4700ms
if (alarm_timer_ms < 300) MOTOR = 1; else MOTOR = 0;
} else if (type == 1) {
// PIR 警报波形:震动 300ms停 200ms再震动 300ms停 4200ms
if (alarm_timer_ms < 300) MOTOR = 1;
else if (alarm_timer_ms >= 300 && alarm_timer_ms < 500) MOTOR = 0;
else if (alarm_timer_ms >= 500 && alarm_timer_ms < 800) MOTOR = 1;
else MOTOR = 0;
} else if (type == 2) {
// 烟感警报波形:急促短闪三下 200ms-150ms-200ms-150ms-200ms
if (alarm_timer_ms < 200) MOTOR = 1;
else if (alarm_timer_ms >= 200 && alarm_timer_ms < 350) MOTOR = 0;
else if (alarm_timer_ms >= 350 && alarm_timer_ms < 550) MOTOR = 1;
else if (alarm_timer_ms >= 550 && alarm_timer_ms < 700) MOTOR = 0;
else if (alarm_timer_ms >= 700 && alarm_timer_ms < 900) MOTOR = 1;
else MOTOR = 0;
} else if (type == 3) {
// 燃气报警波形:快频连续四下
if (alarm_timer_ms < 200) MOTOR = 1;
else if (alarm_timer_ms >= 200 && alarm_timer_ms < 300) MOTOR = 0;
else if (alarm_timer_ms >= 300 && alarm_timer_ms < 500) MOTOR = 1;
else if (alarm_timer_ms >= 500 && alarm_timer_ms < 600) MOTOR = 0;
else if (alarm_timer_ms >= 600 && alarm_timer_ms < 800) MOTOR = 1;
else if (alarm_timer_ms >= 800 && alarm_timer_ms < 900) MOTOR = 0;
else if (alarm_timer_ms >= 900 && alarm_timer_ms < 1100) MOTOR = 1;
else MOTOR = 0;
} else if (type == 4) {
// 水浸警报波形:慢速双长震波 600ms 和 600ms
if (alarm_timer_ms < 600) MOTOR = 1;
else if (alarm_timer_ms >= 600 && alarm_timer_ms < 900) MOTOR = 0;
else if (alarm_timer_ms >= 900 && alarm_timer_ms < 1500) MOTOR = 1;
else MOTOR = 0;
} else {
// 缺省状态默认单次 300ms
if (alarm_timer_ms < 300) MOTOR = 1; else MOTOR = 0;
}
}
/**
* @brief AlarmApp 退出清理
*/
static void AlarmApp_onClose(void)
{
RGB_Send(0, 0, 0, 0, 0, 0); // 关闭灯条
MOTOR = 0; // 停震
current_state = STATE_NORMAL;
XTELL_LOG("[AlarmApp] Closed\r\n");
}
/**
* @brief AlarmApp 事件拦截处理 (按任意键退出报警)
*/
static EventResult AlarmApp_onEvent(SystemEvent *evt)
{
if (evt->key_event != KEY_EVENT_NONE) {
AppManager_SwitchToForeground(&clock_app); // 清除报警并切回时钟待机页
return EVENT_HANDLED;
}
return EVENT_IGNORED;
}

194
App/app_clock.c Normal file
View File

@@ -0,0 +1,194 @@
#include "app_manager.h"
#include "clock.h"
#include "xtell.h"
#include "ui.h"
#include "rgb.h"
#include "event.h"
#include "system.h"
#include "../Drivers/rf.h"
#include "../Drivers/lcd.h"
// 外部引用的全局系统状态变量
extern SystemState current_state;
extern u8 current_hour;
extern u8 current_min;
extern u8 current_sec;
extern volatile u8 clock_updated;
extern volatile u16 ms_tick;
// 待机时钟应用内特有的编辑时间私有变量
static u8 time_edit_active = 0; // 标识当前是否正在编辑修改时间中 (1:是, 0:否)
static u8 time_edit_field = 1; // 标识当前聚焦在哪一个参数上进行微调 (1:修改小时, 2:修改分钟)
static u8 time_edit_blink_on = 1; // 时钟修改时光标闪烁周期状态位
// 声明本应用的生命周期回调函数
static void ClockApp_OnStart(void);
static void ClockApp_onRun(void);
static void ClockApp_onClose(void);
static EventResult ClockApp_onEvent(SystemEvent *evt);
// 实体化定义待机时钟应用控制块
WristbandApp clock_app = {
APP_ID_CLOCK,
"Clock",
ClockApp_OnStart,
ClockApp_onRun,
ClockApp_onClose,
ClockApp_onEvent
};
/**
* @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);
XTELL_LOG("[ClockApp] Time edit started.\r\n");
}
/**
* @brief ClockApp 启动回调
*/
static 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);
XTELL_LOG("[ClockApp] Started\r\n");
}
/**
* @brief ClockApp 轮询主回调
*/
static void ClockApp_onRun(void)
{
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);
}
}
}
/**
* @brief ClockApp 退出清理回调
*/
static void ClockApp_onClose(void)
{
time_edit_active = 0;
RGB_Send(0, 0, 0, 0, 0, 0); // 关闭灯条
XTELL_LOG("[ClockApp] Closed\r\n");
}
/**
* @brief ClockApp 接收事件处理回调 (按键导航与布撤防交互)
*/
static 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_SwitchToForeground(&menu_app);
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;
}
return EVENT_IGNORED; // 其他未被关心的事件交由后台应用或丢弃
}

File diff suppressed because it is too large Load Diff

View File

@@ -3,56 +3,75 @@
#include "config.h"
/* ====== 应用程序管理器 (AppManager) 核心接口 ====== */
// 动态挂载的 WristbandApp 控制块定义
typedef struct WristbandApp {
u8 app_id; // 应用唯一识别 ID
char *app_name; // 应用名称
void (*OnStart)(void); // 启动生命周期,进入前台时调用
void (*onRun)(void); // 轮询生命周期,主循环每次被调度
void (*onClose)(void); // 关闭生命周期,退出前台/后台时清理资源
EventResult (*onEvent)(SystemEvent *evt); // 事件处理生命周期,接收按键事件
} WristbandApp;
/**
* @brief 初始化应用程序管理器
* @details 负责将应用管理器的系统注册表清零,随后逐一注册系统支持的五个前台应用:
* ClockApp (待机时钟), MenuApp (主菜单), PairApp (传感器对码),
* AlarmApp (传感器警报), SOSApp (主动求救呼叫)。最后默认加载并启动 ClockApp。
* @brief 初始化应用管理器
* @details 将前台活跃槽位与后台的三个槽位全部清空为 NULL。
*/
void AppManager_Init(void);
/**
* @brief 启动指定的应用程序
* @param app_id 待启动应用的目标 ID (如 APP_ID_MENU, APP_ID_PAIR)
* @details 会执行完整的切应用生命周期流转:
* 1. 检查目标是否合法且包含有效的 OnStart 启动回调。
* 2. 对当前正在活跃运行的前台应用调用其 onClose(),以关闭该应用的屏幕内容、马达振动或射频特定模式
* 3. 将活跃应用指针指向新应用,将其 is_active 和 is_running 标志位置 1
* 4. 调用新应用的 OnStart() 初始化回调以绘制新页面
* @brief 动态将一个应用切换至前台活跃运行
* @param app 目标应用的结构体指针
* @return bit 1-切换成功0-失败
* @details 流程:
* 1. 若前台已有应用运行,且该应用未挂载在后台,则对其执行 onClose() 关闭生命周期
* 2. 将 fg_app 指针指向新的 app
* 3. 若该 app 目前并未在后台槽位中运行,则执行其 OnStart() 启动生命周期
*/
void AppManager_StartApp(AppID app_id);
bit AppManager_SwitchToForeground(WristbandApp *app);
/**
* @brief 向当前活跃的前台应用程序派发事件
* @param evt 要分发派送的系统事件
* @brief 动态将一个应用挂载到后台槽位
* @param app 目标应用的结构体指针
* @return bit 1-挂载成功0-槽位已满挂载失败
* @details 后台总共支持 3 个槽位。若挂载成功,其 onRun 将会持续在主循环中被轮询。
*/
bit AppManager_AttachToBackground(WristbandApp *app);
/**
* @brief 动态将一个应用从后台卸载
* @param app 目标应用的结构体指针
* @details 从后台槽位数组中移除该指针。若该应用当前也不在前台运行,将对其执行 onClose() 清理外设。
*/
void AppManager_DetachFromBackground(WristbandApp *app);
/**
* @brief 将捕获的事件派发给前台活跃应用
* @param evt 系统派发来的事件
*/
void AppManager_DispatchEvent(SystemEvent evt);
/**
* @brief 在系统主循环中持续轮询运行活跃应用
* @details 负责从全局注册表中定位到 active_app并轮询执行其 onRun() 周期回调
* 该接口同时集成了【CPU占用率限制机制】如果在调用 onRun 前后,检测到应用
* 占用 CPU 时间超过了 APP_ONRUN_MAX_TICKS (5ms),它将在下一次循环中暂时挂起
* 该应用,强制释放本次 CPU 占用以防止界面卡死或影响底层射频解调。
* @brief 在系统主循环中持续轮询轮转前后台所有活跃应用
* @details 负责轮询前台 fg_app 对应的 onRun(),并遍历轮询后台 bg_apps 数组中所有有效应用的 onRun()。
*/
void AppManager_RunActiveApp(void);
/**
* @brief 获取当前前台活跃应用的唯一应用程序 ID
* @return AppID 当前活跃前台应用的枚举ID
* @brief 获取当前前台活跃应用的 ID
* @return u8 当前活跃前台应用的 ID 编号。若无则返回 255 (0xFF)
*/
AppID AppManager_GetActiveAppID(void);
u8 AppManager_GetActiveAppID(void);
// 兼容性接口声明
void AppManager_StartApp(u8 app_id);
/* ====== 时间编辑状态机交互接口 ====== */
/**
* @brief 启动时钟数值调整编辑状态机
* @details 由 MenuApp 的 "HORLOGE" 项被确认点击后触发。用于直接将系统运行状态切入
* STATE_SET_TIME控制灯条常亮蓝色马达短振反馈并进入时间参数微调页。
*/
void Start_Time_Edit(void);
// ====== 系统中各独立 App 实例的外包声明 (各 App 内部进行实体定义) ======
extern WristbandApp clock_app;
extern WristbandApp menu_app;
extern WristbandApp pair_app;
extern WristbandApp alarm_app;
extern WristbandApp sos_app;
extern WristbandApp rf_monitor_app; // 后台射频监控应用
#endif // __APP_MANAGER_H__

187
App/app_menu.c Normal file
View File

@@ -0,0 +1,187 @@
#include "app_manager.h"
#include "xtell.h"
#include "ui.h"
#include "rgb.h"
#include "event.h"
#include "system.h"
#include "../Drivers/rf.h"
// 外部全局变量引用
extern SystemState current_state;
extern u8 menu_select;
extern u8 captured_type;
extern volatile u16 ms_tick;
// 菜单应用私有变量
static u8 menu_level = 0; // 0-主菜单, 1-二级传感器选择菜单, 2-IPC绑定一键发射
static u16 last_bind_tx_ms = 0; // 记录上一次绑定发射的时间戳
// 克隆的手环出厂物理 ID 地址定义 (用于一键绑定)
#define CLONED_ADDR 0x55AAAA
// 声明本应用的生命周期回调函数
static void MenuApp_OnStart(void);
static void MenuApp_onRun(void);
static void MenuApp_onClose(void);
static EventResult MenuApp_onEvent(SystemEvent *evt);
// 实体化定义菜单应用控制块
WristbandApp menu_app = {
APP_ID_MENU,
"Menu",
MenuApp_OnStart,
MenuApp_onRun,
MenuApp_onClose,
MenuApp_onEvent
};
/**
* @brief MenuApp 启动回调
*/
static 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); // 熄灭所有灯条
XTELL_LOG("[MenuApp] Started\r\n");
}
/**
* @brief MenuApp 轮询主回调
*/
static 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~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; // 备份当前时间戳
RF_SetMode(2); // 暂时切换射频芯片为发射调制模式
EV1527_Transmit(CLONED_ADDR, 0x01); // 调制发射手环克隆 ID + 绑定数据码 0x01
RF_SetMode(0); // 切回关闭状态,避免产生噪声
XTELL_LOG("[MenuApp] IPC Binding frame emitted\r\n");
}
}
}
/**
* @brief MenuApp 退出清理回调
*/
static void MenuApp_onClose(void)
{
menu_level = 0;
RGB_Send(0, 0, 0, 0, 0, 0); // 熄灭 RGB
RF_SetMode(1); // 重新开启无线接收模式
XTELL_LOG("[MenuApp] Closed\r\n");
}
/**
* @brief MenuApp 接收按键与控制事件回调
*/
static 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_SwitchToForeground(&clock_app);
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_SwitchToForeground(&clock_app);
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) {
// 确认选择:缓存用户选好的传感器类别,短震反馈并加载对码应用 (PairApp)
captured_type = menu_select;
MOTOR = 1;
Delay_ms(100);
MOTOR = 0;
AppManager_SwitchToForeground(&pair_app); // 切换至 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)
{
// ====== 处于摄像机配对一键绑定发送层 ======
// 长按 ■ 确认键 或 长按 ▼ 键,均表示取消绑定,退出发射并返回一级主菜单
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;
}

231
App/app_pair.c Normal file
View File

@@ -0,0 +1,231 @@
#include "app_manager.h"
#include "xtell.h"
#include "ui.h"
#include "rgb.h"
#include "event.h"
#include "system.h"
#include "database.h"
#include "../Drivers/rf.h"
// 外部全局变量引用
extern SystemState current_state;
extern u8 current_sec;
extern u8 captured_type;
extern u32 captured_addr;
extern volatile u16 ms_tick;
// 对码状态机定义
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; // 对码失败时的起始秒数
static u32 pair_timeout_ms = 0; // 对码超时毫秒计数器
static u16 last_run_tick = 0; // 上一次执行 onRun 时的毫秒滴答数
// 声明本应用的生命周期回调函数
static void PairApp_OnStart(void);
static void PairApp_onRun(void);
static void PairApp_onClose(void);
static EventResult PairApp_onEvent(SystemEvent *evt);
// 实体化定义对码应用控制块
WristbandApp pair_app = {
APP_ID_PAIR,
"Pair",
PairApp_OnStart,
PairApp_onRun,
PairApp_onClose,
PairApp_onEvent
};
/**
* @brief PairApp 启动回调
*/
static void PairApp_OnStart(void)
{
// 1. 为了防止后台射频监听与对码射频接收冲突,将后台射频监听应用暂时卸载
AppManager_DetachFromBackground(&rf_monitor_app);
pair_state = PAIR_STATE_WAIT;
last_radar_sec = 99; // 强制刷写
current_state = STATE_PAIR_WAIT;
pair_timeout_ms = 0;
last_run_tick = ms_tick;
captured_zone = 1;
RF_SetMode(1); // 开启无线接收模式
// 初始化对码搜索页面绘制
UI_ShowPairWaitPage(0);
// 开启白色幻彩双灯珠常亮 (R=150, G=150, B=150),作为对码状态视觉指示
RGB_Send(150, 150, 150, 150, 150, 150);
XTELL_LOG("[PairApp] Started (WAIT)\r\n");
}
/**
* @brief PairApp 轮询主回调
*/
static void PairApp_onRun(void)
{
u32 rx_addr;
u8 rx_data;
u8 flash;
u8 frame;
u16 tick_diff;
// 计算非阻塞的毫秒时间增量
if (ms_tick >= last_run_tick) {
tick_diff = ms_tick - last_run_tick;
} else {
tick_diff = 1000 + ms_tick - last_run_tick;
}
last_run_tick = ms_tick;
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 秒的对码时间上限
pair_timeout_ms += tick_diff;
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);
XTELL_LOG("[PairApp] Timeout fail!\r\n");
return;
}
// ====== 物理射频包解调接收监听 ======
if (EV1527_Decode(&rx_addr, &rx_data)) {
// 保护机制:排除 SOS 码 (0x08)
if (rx_data != 0x08) {
captured_addr = rx_addr; // 缓存捕获的物理 ID 地址 (含数据码)
pair_state = PAIR_STATE_CONFIRM; // 进入防区微调确认模式
current_state = STATE_PAIR_CONFIRM;
RF_SetMode(0); // 暂时关闭物理射频,防止再次覆盖
captured_zone = 1;
UI_ShowPairConfirmPage(captured_type, captured_zone);
XTELL_LOG("[PairApp] Sensor captured. Addr=");
XTELL_LOG_HEX32(rx_addr);
XTELL_LOG("\r\n");
}
}
}
else if (pair_state == PAIR_STATE_CONFIRM) {
// ====== 确认状态:橙色慢闪 (1.5Hz,周期 666ms) ======
u8 flash_orange = (ms_tick / 333) % 2;
if (flash_orange) 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_SwitchToForeground(&menu_app);
}
}
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_SwitchToForeground(&menu_app);
}
}
}
/**
* @brief PairApp 退出清理
*/
static void PairApp_onClose(void)
{
RGB_Send(0, 0, 0, 0, 0, 0); // 关闭 RGB
MOTOR = 0; // 停震
RF_SetMode(1); // 开启无线接收
// 重新挂载后台射频监控应用,以便回到时钟状态下能继续捕获报警
AppManager_AttachToBackground(&rf_monitor_app);
XTELL_LOG("[PairApp] Closed\r\n");
}
/**
* @brief PairApp 事件拦截处理回调
*/
static 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) {
// 短按 ■ 确认键,写入 EEPROM 传感器数据库并保存
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;
XTELL_LOG("[PairApp] Sensor saved successfully.\r\n");
return EVENT_HANDLED;
}
if (evt->key_event == KEY_EVENT_CONFIRM_LONG) {
// 长按 ■ 确认键放弃本次捕获,直接返回二级主菜单
RGB_Send(0, 0, 0, 0, 0, 0);
AppManager_SwitchToForeground(&menu_app);
return EVENT_HANDLED;
}
}
// 在对码搜寻或确认防区期间,长按 ▼ 键均代表强行取消对码直接退回菜单页
if (evt->key_event == KEY_EVENT_DOWN_LONG) {
RGB_Send(0, 0, 0, 0, 0, 0);
AppManager_SwitchToForeground(&menu_app);
return EVENT_HANDLED;
}
return EVENT_IGNORED;
}

106
App/app_rf_monitor.c Normal file
View File

@@ -0,0 +1,106 @@
#include "app_manager.h"
#include "xtell.h"
#include "event.h"
#include "database.h"
#include "system.h"
#include "../Drivers/rf.h"
// 外部全局变量引用
extern SystemState current_state;
extern u32 captured_addr;
extern u8 alarm_sensor_slot;
extern Sensor_Slot xdata sensor_list[16];
// 声明本应用的生命周期回调函数
static void RfMonitorApp_OnStart(void);
static void RfMonitorApp_onRun(void);
static void RfMonitorApp_onClose(void);
static EventResult RfMonitorApp_onEvent(SystemEvent *evt);
// 实体化定义后台射频监控应用控制块
WristbandApp rf_monitor_app = {
0xFE, // 后台应用 ID 设为 0xFE
"RF_Monitor",
RfMonitorApp_OnStart,
RfMonitorApp_onRun,
RfMonitorApp_onClose,
RfMonitorApp_onEvent
};
/**
* @brief RfMonitorApp 启动回调
*/
static void RfMonitorApp_OnStart(void)
{
RF_SetMode(1); // 开启天线并设为接收模式
XTELL_LOG("[RF_Monitor] Background RF monitor started.\r\n");
}
/**
* @brief RfMonitorApp 轮询主回调
* @details 负责在后台静默轮询物理射频 EV1527 解码器。
* 解码成功后在本地传感器数据库中匹配,并执行布撤防及防区过滤逻辑,
* 匹配通过后一键拉起前台报警应用。
*/
static void RfMonitorApp_onRun(void)
{
u32 rx_addr;
u8 rx_data;
u8 slot;
// 常态对待机下的射频检测与触发过滤逻辑
if (EV1527_Decode(&rx_addr, &rx_data)) {
XTELL_LOG("[RF_Monitor] Decoded Addr=");
XTELL_LOG_HEX32(rx_addr);
XTELL_LOG(", Data=");
XTELL_LOG_HEX8(rx_data);
XTELL_LOG("\r\n");
// 比对收到的 24 位射频源地址是否合法注册在手环本地数据库中
if (Check_Sensor_ID(rx_addr, &slot)) {
u8 sensor_type = sensor_list[slot].type; // 获取注册的设备类型
if (sensor_type < 5) {
// 防区分类过滤与布撤防过滤逻辑:
// - zone == 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)
{
// 只有在前台不为紧急 SOS 呼救且当前不处于报警状态时,才执行报警调起
if (AppManager_GetActiveAppID() != APP_ID_SOS &&
AppManager_GetActiveAppID() != APP_ID_ALARM)
{
captured_addr = rx_addr; // 缓存报警地址
alarm_sensor_slot = slot; // 缓存报警槽位号
XTELL_LOG("[RF_Monitor] Intruder ALERT! Waking up alarm app...\r\n");
AppManager_SwitchToForeground(&alarm_app); // 动态切入警报前台
}
} else {
XTELL_LOG("[RF_Monitor] Ignored due to DISARMED state.\r\n");
}
}
} else {
XTELL_LOG("[RF_Monitor] Unregistered sensor address.\r\n");
}
}
}
/**
* @brief RfMonitorApp 退出清理
*/
static void RfMonitorApp_onClose(void)
{
XTELL_LOG("[RF_Monitor] Background RF monitor closed.\r\n");
}
/**
* @brief RfMonitorApp 事件处理 (后台应用暂不拦截处理按键事件)
*/
static EventResult RfMonitorApp_onEvent(SystemEvent *evt)
{
(void)evt;
return EVENT_IGNORED;
}

137
App/app_sos.c Normal file
View File

@@ -0,0 +1,137 @@
#include "app_manager.h"
#include "xtell.h"
#include "ui.h"
#include "rgb.h"
#include "event.h"
#include "system.h"
#include "../Drivers/rf.h"
#include "../Drivers/lcd.h"
// 外部全局变量引用
extern SystemState current_state;
extern volatile u16 ms_tick;
extern volatile bit alarm_flash_flag;
// 克隆的手环出厂物理 ID 地址
#define CLONED_ADDR 0x55AAAA
// 本地私有变量
static u8 sos_last_phase = 99; // 记录上一次发射的 500ms 半周期状态
static u32 alarm_timer_ms = 0; // SOS 马达震动波形计数器
static u16 last_run_tick = 0; // 记录上一次轮询的 ticks
// 声明本应用的生命周期回调函数
static void SOSApp_OnStart(void);
static void SOSApp_onRun(void);
static void SOSApp_onClose(void);
static EventResult SOSApp_onEvent(SystemEvent *evt);
// 实体化定义 SOS 呼救应用控制块
WristbandApp sos_app = {
APP_ID_SOS,
"SOS",
SOSApp_OnStart,
SOSApp_onRun,
SOSApp_onClose,
SOSApp_onEvent
};
/**
* @brief SOSApp 启动回调
*/
static void SOSApp_OnStart(void)
{
current_state = STATE_SOS_EMITTED; // 进入 SOS 紧急求救状态
sos_last_phase = 99;
alarm_timer_ms = 0; // 马达震动计数器清零
last_run_tick = ms_tick;
UI_ShowSosPage(); // 初始化渲染 SOS 警示主页
// 立即点亮左侧灯为红色,右侧熄灭,准备开启双色红灯交替爆闪
RGB_Send(0, 200, 0, 0, 0, 0);
XTELL_LOG("[SOSApp] Started\r\n");
}
/**
* @brief SOSApp 轮询主回调
* @details 包含 500ms 周期的高频射频 SOS 码发射、红蓝交替爆闪以及
* 马达 (1000ms震动 / 200ms静音) 规律震动控制。
*/
static void SOSApp_onRun(void)
{
u16 tick_diff;
// ====== 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); // 释放接收
XTELL_LOG("[SOSApp] SOS Radio packet emitted\r\n");
}
// ====== 2. 控制 RGB 双灯珠以 50ms 频率进行高频左右交替红蓝爆闪 ======
{
u8 flash = (ms_tick / 50) % 2;
if (flash) {
RGB_Send(0, 200, 0, 0, 0, 200); // 灯 1 亮红 (GRB 中 R=200),灯 2 亮蓝 (B=200)
} else {
RGB_Send(0, 0, 200, 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);
}
// ====== 4. 马达 SOS 震动节拍自处理 (1000ms 震动200ms 静音) ======
if (ms_tick >= last_run_tick) {
tick_diff = ms_tick - last_run_tick;
} else {
tick_diff = 1000 + ms_tick - last_run_tick;
}
last_run_tick = ms_tick;
alarm_timer_ms += tick_diff;
if (alarm_timer_ms >= 1200) {
alarm_timer_ms = 0;
}
if (alarm_timer_ms < 1000) {
MOTOR = 1;
} else {
MOTOR = 0;
}
}
/**
* @brief SOSApp 退出清理
*/
static void SOSApp_onClose(void)
{
RGB_Send(0, 0, 0, 0, 0, 0); // 关闭 RGB
MOTOR = 0; // 停震
current_state = STATE_NORMAL;
XTELL_LOG("[SOSApp] Closed\r\n");
}
/**
* @brief SOSApp 事件拦截处理回调 (按任意键退出 SOS)
*/
static EventResult SOSApp_onEvent(SystemEvent *evt)
{
if (evt->key_event != KEY_EVENT_NONE) {
AppManager_SwitchToForeground(&clock_app); // 退出 SOS返回时钟
return EVENT_HANDLED;
}
return EVENT_IGNORED;
}

37
App/clock.c Normal file
View File

@@ -0,0 +1,37 @@
#include "clock.h"
#include "xtell.h"
// 软时钟基础计时变量
u8 current_hour = 10; // 系统软时钟:小时 (默认初始化为 10 点)
u8 current_min = 35; // 系统软时钟:分钟 (默认初始化为 35 分)
u8 current_sec = 0; // 系统软时钟:秒
volatile u8 clock_updated = 0; // 分钟跳变标志位,置位后用于驱动 UI 刷新时间显示
// 毫秒计数器 (Timer1 每 1ms 累加)
static u16 ms_cnt = 0;
/**
* @brief 系统软时钟毫秒递增更新 (在 1ms 中断中调用)
* @details 负责由定时器 1ms 驱动递增软时钟,并在分钟发生跳变时将 clock_updated 置位
*/
void Clock_IncMS(void)
{
ms_cnt++;
if (ms_cnt >= 1000) {
ms_cnt = 0; // 1秒时间到
current_sec++;
if (current_sec >= 60) {
current_sec = 0; // 1分钟时间到
current_min++;
if (current_min >= 60) {
current_min = 0; // 1小时时间到
current_hour++;
if (current_hour >= 24) {
current_hour = 0; // 小时循环
}
}
clock_updated = 1; // 分钟跳变,标志位置位,前台待机 App 会捕获以刷新 UI
XTELL_LOG("[CLOCK] Time updated!\r\n");
}
}
}

17
App/clock.h Normal file
View File

@@ -0,0 +1,17 @@
#ifndef __CLOCK_H__
#define __CLOCK_H__
#include "config.h"
// 全局软时钟变量声明
extern u8 current_hour;
extern u8 current_min;
extern u8 current_sec;
extern volatile u8 clock_updated;
/**
* @brief 系统软时钟毫秒递增更新 (在 1ms 中断中调用)
*/
void Clock_IncMS(void);
#endif // __CLOCK_H__

View File

@@ -68,18 +68,9 @@ typedef enum {
KEY_EVENT_CONFIRM_LONG // ■ 确认键长按事件 (打开主菜单)
} KeyEvent;
/* ====== 事件优先级枚举 ====== */
typedef enum {
EVENT_PRIORITY_LOW, // 低优先级:系统内部状态更新 (如时钟分钟更新)
EVENT_PRIORITY_NORMAL, // 正常优先级:用户按键交互、菜单导航
EVENT_PRIORITY_HIGH, // 高优先级:无线传感器入侵警报
EVENT_PRIORITY_URGENT // 紧急优先级SOS 主动求救
} EventPriority;
/* ====== 系统事件结构体定义 ====== */
typedef struct {
KeyEvent key_event; // 关联的按键事件类型
EventPriority priority; // 事件的处理优先级
u32 extra_data; // 随事件携带的额外数据 (如 24 位射频源地址等)
u8 extra_size; // 额外数据的有效字节大小 (0~4)
} SystemEvent;

View File

@@ -2,6 +2,8 @@
#include "app_manager.h"
#include "database.h"
#include "system.h"
#include "clock.h"
#include "xtell.h"
// 定义事件环形队列的最大深度为 8
#define EVENT_QUEUE_DEPTH 8
@@ -64,7 +66,6 @@ SystemEvent EventQueue_Pop(void)
SystemEvent evt;
// 默认空事件属性
evt.key_event = KEY_EVENT_NONE;
evt.priority = EVENT_PRIORITY_LOW;
evt.extra_data = 0;
evt.extra_size = 0;
@@ -118,46 +119,17 @@ void Event_Dispatcher_Loop(void)
inactivity_timer = 0; // 捕获到有效事件,清零休眠计时器,重置闲置超时
// 串口打印调试信息
Uart_SendString("[EVENT] key=");
Uart_SendHex8((u8)evt.key_event);
Uart_SendString(", pri=");
Uart_SendHex8((u8)evt.priority);
Uart_SendString("\r\n");
XTELL_LOG("[EVENT] key=");
XTELL_LOG_HEX8((u8)evt.key_event);
XTELL_LOG("\r\n");
// SOS 按键紧急处理:若非处于 SOS 呼救状态下,接收到 SOS 键动作,立即强行切入到 SOS_APP 并执行求救
// SOS 按键紧急处理:若当前前台不为 SOS 应用,接收到 SOS 键动作,立即强行切入到 SOS_APP 并执行求救
if ((evt.key_event == KEY_EVENT_SOS_CLICK || evt.key_event == KEY_EVENT_SOS_LONG) &&
AppManager_GetActiveAppID() != APP_ID_SOS) {
AppManager_StartApp(APP_ID_SOS);
return;
}
// 传感器入侵报警过滤与跳转处理:
// 当接收到高优先级且带有 4 字节射频地址的数据包,说明有传感器触发了发射
if (evt.priority == EVENT_PRIORITY_HIGH && evt.extra_size >= 4) {
u8 slot;
// 在本地数据库中检索该传感器地址是否合法登记过
if (Check_Sensor_ID(evt.extra_data, &slot)) {
// 在不处于 SOS 的情况下,可正常强行切入 ALARM_APP 触发声光振警报
if (AppManager_GetActiveAppID() != APP_ID_SOS) {
captured_addr = evt.extra_data; // 缓存报警地址
alarm_sensor_slot = slot; // 缓存报警槽位
AppManager_StartApp(APP_ID_ALARM); // 强制切入报警前台
return;
}
}
}
// 无线对码捕获处理:
// 若系统正处于对码应用 (APP_ID_PAIR),遇到普通优先级无线射频包,则将其派发给该应用进行匹配与登记
if (evt.priority == EVENT_PRIORITY_NORMAL && evt.extra_size >= 4) {
captured_addr = evt.extra_data; // 缓存捕获的待配对地址
captured_type = menu_select; // 将主菜单选择的类型传给配对缓存
if (AppManager_GetActiveAppID() == APP_ID_PAIR) {
AppManager_DispatchEvent(evt); // 派发给对码应用
return;
}
}
// 常规事件(如普通的按键点击)直接透传分发给当前处于前台活跃的应用
AppManager_DispatchEvent(evt);
}
@@ -182,89 +154,14 @@ void Timer1_Init(void)
/**
* @brief 定时器 1 中断服务程序 (每 1ms 触发一次)
* @details 承担了系统软时钟计算、报警与求救下的马达控制状态机以及 10ms 周期性按键消抖扫描的功能。
* @details 承担了系统软时钟计算、10ms 周期性按键消抖扫描的功能。马达等特定业务已剥离至 App 内部。
*/
void Timer1_Isr(void) interrupt 3
{
SystemEvent evt;
// 1ms 递增滴答
ms_tick++;
if (ms_tick >= 1000) {
ms_tick = 0; // 1秒时间已到归零
current_sec++;
if (current_sec >= 60) {
current_sec = 0; // 1分钟时间已到
current_min++;
if (current_min >= 60) {
current_min = 0; // 1小时时间已到
current_hour++;
if (current_hour >= 24) current_hour = 0; // 小时循环
}
clock_updated = 1; // 标志位置1提示前台时钟应用刷新显示
}
}
// 如果处于对码等待状态,对码超时计时器增加 (30秒超时判断)
if (current_state == STATE_PAIR_WAIT) {
pair_timeout_ms++;
}
/* 振动马达定时状态机逻辑控制 */
if (current_state == STATE_SOS_EMITTED) {
// SOS 模式:交替规律震动 (震动 1000ms静音 200ms)
alarm_timer_ms++;
if (alarm_timer_ms >= 1200) alarm_timer_ms = 0;
if (alarm_timer_ms < 1000) MOTOR = 1; else MOTOR = 0;
} else if (current_state == STATE_ALARM) {
// 报警状态:根据触发警报的传感器类型,输出对应的差异化震动波形以便触觉分辨
u8 stype = sensor_list[alarm_sensor_slot].type;
alarm_timer_ms++;
if (alarm_timer_ms >= 5000) alarm_timer_ms = 0; // 5秒一周期
if (stype == 0) {
// 门磁警报波形:单次震动 300ms停 4700ms
if (alarm_timer_ms < 300) MOTOR = 1; else MOTOR = 0;
} else if (stype == 1) {
// PIR 警报波形:震动 300ms停 200ms再震动 300ms停 4200ms
if (alarm_timer_ms < 300) MOTOR = 1;
else if (alarm_timer_ms >= 300 && alarm_timer_ms < 500) MOTOR = 0;
else if (alarm_timer_ms >= 500 && alarm_timer_ms < 800) MOTOR = 1;
else MOTOR = 0;
} else if (stype == 2) {
// 烟感警报波形:急促短闪三下 200ms-150ms-200ms-150ms-200ms
if (alarm_timer_ms < 200) MOTOR = 1;
else if (alarm_timer_ms >= 200 && alarm_timer_ms < 350) MOTOR = 0;
else if (alarm_timer_ms >= 350 && alarm_timer_ms < 550) MOTOR = 1;
else if (alarm_timer_ms >= 550 && alarm_timer_ms < 700) MOTOR = 0;
else if (alarm_timer_ms >= 700 && alarm_timer_ms < 900) MOTOR = 1;
else MOTOR = 0;
} else if (stype == 3) {
// 燃气报警波形:快频连续四下
if (alarm_timer_ms < 200) MOTOR = 1;
else if (alarm_timer_ms >= 200 && alarm_timer_ms < 300) MOTOR = 0;
else if (alarm_timer_ms >= 300 && alarm_timer_ms < 500) MOTOR = 1;
else if (alarm_timer_ms >= 500 && alarm_timer_ms < 600) MOTOR = 0;
else if (alarm_timer_ms >= 600 && alarm_timer_ms < 800) MOTOR = 1;
else if (alarm_timer_ms >= 800 && alarm_timer_ms < 900) MOTOR = 0;
else if (alarm_timer_ms >= 900 && alarm_timer_ms < 1100) MOTOR = 1;
else MOTOR = 0;
} else if (stype == 4) {
// 水浸警报波形:慢速双长震波 600ms 和 600ms
if (alarm_timer_ms < 600) MOTOR = 1;
else if (alarm_timer_ms >= 600 && alarm_timer_ms < 900) MOTOR = 0;
else if (alarm_timer_ms >= 900 && alarm_timer_ms < 1500) MOTOR = 1;
else MOTOR = 0;
} else {
// 缺省状态默认单次 300ms
if (alarm_timer_ms < 300) MOTOR = 1; else MOTOR = 0;
}
} else {
// 其他常态下彻底关闭震动马达并归零计时器
MOTOR = 0;
alarm_timer_ms = 0;
motor_timer_ms = 0;
}
// 1ms 定时更新软时钟
Clock_IncMS();
// 报警屏幕红边框以 100ms 周期亮灭,进行颜色爆闪指示
alarm_flash_flag = (ms_tick / 100) % 2;
@@ -294,12 +191,11 @@ void Timer1_Isr(void) interrupt 3
evt.extra_data = 0;
evt.extra_size = 0;
// 双键组合判定:▲ 键 ▼ 键同时按下的消抖与生成
// 双键组合判定:▲ 键 and ▼ 键同时按下的消抖与生成
if (raw_up && raw_down) {
comb_hold++;
if (comb_hold == 300) { // 持续按下达 3 秒 (300 * 10ms = 3s)
evt.key_event = KEY_EVENT_UP_DOWN_COMB;
evt.priority = EVENT_PRIORITY_NORMAL;
EventQueue_Push(evt); // 压入队列
}
key_up_hold = 0; // 清零单个按键计数,防止双击事件干扰
@@ -312,14 +208,12 @@ void Timer1_Isr(void) interrupt 3
key_up_hold++;
if (key_up_hold == 300) { // 达到 3 秒判定为长按
evt.key_event = KEY_EVENT_UP_LONG;
evt.priority = EVENT_PRIORITY_NORMAL;
EventQueue_Push(evt);
}
} else {
// 如果松开时按下计数在消抖阈值20ms至长按阈值3s之间则判定为一次短按
if (key_up_hold >= 2 && key_up_hold < 300) {
evt.key_event = KEY_EVENT_UP_CLICK;
evt.priority = EVENT_PRIORITY_NORMAL;
EventQueue_Push(evt);
}
key_up_hold = 0; // 释放归零
@@ -330,13 +224,11 @@ void Timer1_Isr(void) interrupt 3
key_down_hold++;
if (key_down_hold == 300) { // 长按 3 秒
evt.key_event = KEY_EVENT_DOWN_LONG;
evt.priority = EVENT_PRIORITY_NORMAL;
EventQueue_Push(evt);
}
} else {
if (key_down_hold >= 2 && key_down_hold < 300) {
evt.key_event = KEY_EVENT_DOWN_CLICK;
evt.priority = EVENT_PRIORITY_NORMAL;
EventQueue_Push(evt);
}
key_down_hold = 0; // 释放归零
@@ -347,13 +239,11 @@ void Timer1_Isr(void) interrupt 3
key_confirm_hold++;
if (key_confirm_hold == 300) { // 长按 3 秒打开主菜单
evt.key_event = KEY_EVENT_CONFIRM_LONG;
evt.priority = EVENT_PRIORITY_NORMAL;
EventQueue_Push(evt);
}
} else {
if (key_confirm_hold >= 2 && key_confirm_hold < 300) {
evt.key_event = KEY_EVENT_CONFIRM_CLICK;
evt.priority = EVENT_PRIORITY_NORMAL;
EventQueue_Push(evt);
}
key_confirm_hold = 0; // 释放归零
@@ -362,15 +252,13 @@ void Timer1_Isr(void) interrupt 3
// SOS 物理求救按键状态机
if (raw_sos) {
key_sos_hold++;
if (key_sos_hold == 300) { // 长按 3 秒,发出 URGENT 优先级求救,插入队首
if (key_sos_hold == 300) { // 长按 3 秒,发出求救,插入队首
evt.key_event = KEY_EVENT_SOS_LONG;
evt.priority = EVENT_PRIORITY_URGENT;
EventQueue_InsertFront(evt);
}
} else {
if (key_sos_hold >= 2 && key_sos_hold < 300) { // 短按即呼救,插入队首
evt.key_event = KEY_EVENT_SOS_CLICK;
evt.priority = EVENT_PRIORITY_URGENT;
EventQueue_InsertFront(evt);
}
key_sos_hold = 0; // 释放归零

View File

@@ -5,17 +5,20 @@
#include "database.h"
#include "ui.h"
#include "system.h"
#include "clock.h"
#include "xtell.h"
#include "../Drivers/rf.h"
#include "../Drivers/lcd.h"
// 声明外部射频模拟变量
extern volatile u32 mock_rx_addr;
extern volatile u8 mock_rx_data;
extern volatile bit mock_rf_ready;
/* =========================================================================
* 系统全局变量定义
* ========================================================================= */
SystemState current_state = STATE_NORMAL; // 系统整体的当前运行状态,默认为正常时钟待机页
u8 current_hour = 10; // 系统软时钟:小时 (默认初始化为 10 点)
u8 current_min = 35; // 系统软时钟:分钟 (默认初始化为 35 分)
u8 current_sec = 0; // 系统软时钟:秒
volatile u8 clock_updated = 0; // 分钟跳变标志位,置位后用于驱动 UI 刷新时间显示
u8 menu_select = 0; // 主菜单当前选中的行高亮索引 (0~2)
u32 captured_addr = 0; // 配对或报警时,捕获到的传感器 24 位 EV1527 射频物理地址
@@ -46,17 +49,6 @@ volatile u16 comb_hold = 0; // ▲ + ▼ 组合键按下时长累
* ========================================================================= */
/**
* @brief 解析通过串口 1 发送来的键盘及测试模拟指令
* @param cmd 接收到的 ASCII 字符命令
* @details 负责将字符指令转化对应的系统按键或射频事件,并推入事件队列以测试软件架构:
* - 'u'/'U': 模拟短按/长按 ▲ 键。
* - 'd'/'D': 模拟短按/长按 ▼ 键。
* - 's'/'S': 模拟短按/长按 SOS 物理键(优先插入队首)。
* - 'c'/'C': 模拟组合键触发。
* - 'a'/'A': 模拟无线传感器入侵触发警报信号,地址固定为 0x123456zone=1。
* - 'p'/'P': 模拟对码期间捕获的 24 位无线对码信号,地址固定为 0x789ABC。
* - '1'/'2'/'3': 模拟系统的正常、已布防、已撤防状态切换。
* - 'z'/'Z': 模拟超时,强行使闲置计数器置 6000触发自动深度休眠。
* - 't'/'T': 触发屏幕与无线控制引脚测试。
*/
void Debug_ProcessCommand(char cmd)
{
@@ -67,39 +59,32 @@ void Debug_ProcessCommand(char cmd)
if (cmd == 'u') {
debug_evt.key_event = KEY_EVENT_UP_CLICK;
debug_evt.priority = EVENT_PRIORITY_NORMAL;
EventQueue_Push(debug_evt); // 压入队尾
Uart_SendString("[UART] KEY_UP Clicked\r\n");
XTELL_LOG("[UART] KEY_UP Clicked\r\n");
} else if (cmd == 'U') {
debug_evt.key_event = KEY_EVENT_UP_LONG;
debug_evt.priority = EVENT_PRIORITY_NORMAL;
EventQueue_Push(debug_evt);
Uart_SendString("[UART] KEY_UP Long Pressed\r\n");
XTELL_LOG("[UART] KEY_UP Long Pressed\r\n");
} else if (cmd == 'd') {
debug_evt.key_event = KEY_EVENT_DOWN_CLICK;
debug_evt.priority = EVENT_PRIORITY_NORMAL;
EventQueue_Push(debug_evt);
Uart_SendString("[UART] KEY_DOWN Clicked\r\n");
XTELL_LOG("[UART] KEY_DOWN Clicked\r\n");
} else if (cmd == 'D') {
debug_evt.key_event = KEY_EVENT_DOWN_LONG;
debug_evt.priority = EVENT_PRIORITY_NORMAL;
EventQueue_Push(debug_evt);
Uart_SendString("[UART] KEY_DOWN Long Pressed\r\n");
XTELL_LOG("[UART] KEY_DOWN Long Pressed\r\n");
} else if (cmd == 's') {
debug_evt.key_event = KEY_EVENT_SOS_CLICK;
debug_evt.priority = EVENT_PRIORITY_URGENT;
EventQueue_InsertFront(debug_evt); // 紧急事件插入队首
Uart_SendString("[UART] KEY_SOS Clicked\r\n");
XTELL_LOG("[UART] KEY_SOS Clicked\r\n");
} else if (cmd == 'S') {
debug_evt.key_event = KEY_EVENT_SOS_LONG;
debug_evt.priority = EVENT_PRIORITY_URGENT;
EventQueue_InsertFront(debug_evt);
Uart_SendString("[UART] KEY_SOS Long Pressed\r\n");
XTELL_LOG("[UART] KEY_SOS Long Pressed\r\n");
} else if (cmd == 'c' || cmd == 'C') {
debug_evt.key_event = KEY_EVENT_UP_DOWN_COMB;
debug_evt.priority = EVENT_PRIORITY_NORMAL;
EventQueue_Push(debug_evt);
Uart_SendString("[UART] KEY_UP+DOWN Combined\r\n");
XTELL_LOG("[UART] KEY_UP+DOWN Combined\r\n");
} else if (cmd == 'a' || cmd == 'A') {
u32 mock_addr = 0x123456; // 设定模拟的报警射频源地址
u8 mock_slot;
@@ -107,48 +92,39 @@ void Debug_ProcessCommand(char cmd)
if (!Check_Sensor_ID(mock_addr, &mock_slot)) {
Add_Sensor_With_Zone(mock_addr, 0, 1);
}
debug_evt.key_event = KEY_EVENT_NONE;
debug_evt.priority = EVENT_PRIORITY_HIGH; // 入侵报警设为 HIGH 优先级
debug_evt.extra_data = mock_addr;
debug_evt.extra_size = 4;
EventQueue_InsertFront(debug_evt); // 高优先级插队
Uart_SendString("[UART] Mock RF Alarm\r\n");
mock_rx_addr = mock_addr;
mock_rx_data = 0x01;
mock_rf_ready = 1; // 触发射频模拟
XTELL_LOG("[UART] Mock RF Alarm Triggered\r\n");
} else if (cmd == 'p' || cmd == 'P') {
// 只有当手环切入 Pair 模式时,模拟对码信号才有效
if (AppManager_GetActiveAppID() == APP_ID_PAIR) {
debug_evt.key_event = KEY_EVENT_NONE;
debug_evt.priority = EVENT_PRIORITY_NORMAL;
debug_evt.extra_data = 0x789ABC; // 设定模拟对码硬件地址
debug_evt.extra_size = 4;
EventQueue_Push(debug_evt);
Uart_SendString("[UART] Mock RF Pair Signal\r\n");
} else {
Uart_SendString("[UART] Not in PAIR mode\r\n");
}
mock_rx_addr = 0x789ABC;
mock_rx_data = 0x01;
mock_rf_ready = 1; // 触发射频模拟
XTELL_LOG("[UART] Mock RF Pair Triggered\r\n");
} else if (cmd == '1') {
// 模拟切换至 正常撤防 (NORMAL) 状态并刷新时钟
if (AppManager_GetActiveAppID() == APP_ID_CLOCK) {
current_state = STATE_NORMAL;
UI_ShowClockPage(current_state, current_hour, current_min);
}
Uart_SendString("[UART] State: NORMAL\r\n");
XTELL_LOG("[UART] State: NORMAL\r\n");
} else if (cmd == '2') {
// 模拟切换至 已布防 (ARMED) 状态,普通门磁可触发警报
if (AppManager_GetActiveAppID() == APP_ID_CLOCK) {
current_state = STATE_ARMED;
UI_ShowClockPage(current_state, current_hour, current_min);
}
Uart_SendString("[UART] State: ARMED\r\n");
XTELL_LOG("[UART] State: ARMED\r\n");
} else if (cmd == '3') {
// 模拟切换至 已撤防 (DISARMED) 状态,忽略普通报警
if (AppManager_GetActiveAppID() == APP_ID_CLOCK) {
current_state = STATE_DISARMED;
UI_ShowClockPage(current_state, current_hour, current_min);
}
Uart_SendString("[UART] State: DISARMED\r\n");
XTELL_LOG("[UART] State: DISARMED\r\n");
} else if (cmd == 'z' || cmd == 'Z') {
inactivity_timer = 6000; // 模拟闲置 60 秒超时,强制触发主循环深度休眠
Uart_SendString("[UART] Force Entering Sleep mode!\r\n");
XTELL_LOG("[UART] Force Entering Sleep mode!\r\n");
} else if (cmd == 't' || cmd == 'T') {
Loopback_Test(); // 调用回环测试验证屏幕
RF_Test_Pin(); // 无线射频测试
@@ -176,20 +152,24 @@ void main(void)
Delay_ms(50); // 等待电压充盈稳定,避开浪涌
LCD_Init(); // 初始化 Truly AMOLED 屏幕控制寄存器序列
Uart1_Init(); // 初始化串口 1 波特率 115200 供调试日志
Uart_SendString("Wristband System Initialized!\r\n");
XTELL_LOG("Wristband System Initialized!\r\n");
Load_Database(); // 从 IAP Flash 第 254 扇区读取所有已绑定的传感器数据至 RAM
/* 步骤 1: 初始化事件环形缓冲区队列 */
EventQueue_Init();
/* 步骤 2+5: 初始化应用管理器并登记所有前台应用,默认加载 ClockApp */
/* 步骤 2: 初始化应用管理器 */
AppManager_Init();
/* 步骤 3: 动态挂载待机时钟应用到前台,并挂载后台射频监控应用 */
AppManager_SwitchToForeground(&clock_app);
AppManager_AttachToBackground(&rf_monitor_app);
/* 启动 1ms 系统基准定时器 Timer1 */
Timer1_Init();
Uart_SendString("[SYS] Event-driven framework ready\r\n");
XTELL_LOG("[SYS] Event-driven framework ready\r\n");
// ====== 主循环 ======
while (1) {
@@ -204,18 +184,19 @@ void main(void)
Event_Dispatcher_Loop();
}
/* 3. 应用调度中心:运行当前处于前台活跃状态的应用(集成了 CPU 运行时间超时挂起保护) */
/* 3. 应用调度中心:运行前后台所有已挂载激活的应用 */
AppManager_RunActiveApp();
/* 4. 自动休眠判定:常态无操作闲置达到 60 秒 (6000 * 10ms = 60s) 时,切入低功耗停机休眠 */
if (inactivity_timer >= 6000) {
inactivity_timer = 0;
current_state = STATE_SLEEP; // 系统置为休眠状态
Enter_Low_Power_Sleep(); // 该函数是阻塞的MCU 将在此挂起。唤醒后将自动从其后继续执行
Enter_Low_Power_Sleep(); // 挂起 MCU
}
}
}
// 唤醒源事件标志,由对应的外部中断 ISR 写入
volatile u8 p0_wakeup_flag = 0; // P0 端口按键唤醒标志
volatile u8 p2_wakeup_flag = 0; // P2 端口按键唤醒标志

View File

@@ -1,6 +1,8 @@
#include "system.h"
#include "rgb.h"
#include "ui.h"
#include "clock.h"
#include "xtell.h"
#include "../Drivers/lcd.h"
#include "../Drivers/rf.h"
#include "app_manager.h"
@@ -268,7 +270,7 @@ void FormatHex(u32 val, char *buf)
*/
void Enter_Low_Power_Sleep(void)
{
Uart_SendString("[SYS] Entering low power sleep...\r\n");
XTELL_LOG("[SYS] Entering low power sleep...\r\n");
// 1. 关闭/熄灭 FCOB 灯条
RGB_Send(0, 0, 0, 0, 0, 0);
@@ -343,7 +345,7 @@ void Enter_Low_Power_Sleep(void)
EA = 1;
// 13. 向电源控制寄存器 PCON 写入 0x02使 MCU 核心进入 Power-Down 模式CPU 停止运转
Uart_SendString("[SYS] Entering Power-Down mode...\r\n");
XTELL_LOG("[SYS] Entering Power-Down mode...\r\n");
PCON |= 0x02; // 设定 PD 位,硬件立刻休眠挂起
// 唤醒后必须跟 _nop_() 空操作以占位和刷新指令流水线
@@ -388,20 +390,20 @@ void Wakeup_Restore(void)
// 打印日志,判断并告知调试串口具体是哪个物理源将手环唤醒的
if ((p0_wakeup_flag & 0x0E) || (p2_wakeup_flag & 0x40))
{
Uart_SendString("[WR] Woken by KEY (P0=");
Uart_SendHex8(p0_wakeup_flag);
Uart_SendString(", P2=");
Uart_SendHex8(p2_wakeup_flag);
Uart_SendString(")\r\n");
XTELL_LOG("[WR] Woken by KEY (P0=");
XTELL_LOG_HEX8(p0_wakeup_flag);
XTELL_LOG(", P2=");
XTELL_LOG_HEX8(p2_wakeup_flag);
XTELL_LOG(")\r\n");
}
else if ((p2_wakeup_flag & 0x02) || rf_wakeup_flag)
{
rf_wakeup_flag = 1; // 确认标记为射频解调唤醒
Uart_SendString("[WR] Woken by RF (P2.1)!\r\n");
XTELL_LOG("[WR] Woken by RF (P2.1)!\r\n");
}
else
{
Uart_SendString("[WR] Woken by TIMER/Other\r\n");
XTELL_LOG("[WR] Woken by TIMER/Other\r\n");
}
// 5. 【屏幕供电软启动】:
@@ -422,7 +424,7 @@ void Wakeup_Restore(void)
// 9. 更新运行状态为普通时钟,并强制调用时钟界面函数刷屏,返回工作状态
current_state = STATE_NORMAL;
UI_ShowClockPage(current_state, current_hour, current_min);
Uart_SendString("[SYS] Wakeup restored!\r\n");
XTELL_LOG("[SYS] Wakeup restored!\r\n");
}
/**

24
App/xtell.h Normal file
View File

@@ -0,0 +1,24 @@
#ifndef __XTELL_H__
#define __XTELL_H__
#include "config.h"
// 全局调试日志开关
#define XTELL_DEBUG_ENABLE
// 声明底层串口发送函数 (由 system.c 提供)
extern void Uart_SendString(char *p);
extern void Uart_SendHex8(u8 val);
extern void Uart_SendHex32(u32 val);
#ifdef XTELL_DEBUG_ENABLE
#define XTELL_LOG(s) Uart_SendString(s)
#define XTELL_LOG_HEX8(v) Uart_SendHex8(v)
#define XTELL_LOG_HEX32(v) Uart_SendHex32(v)
#else
#define XTELL_LOG(s)
#define XTELL_LOG_HEX8(v)
#define XTELL_LOG_HEX32(v)
#endif
#endif // __XTELL_H__