From 56b92a8edec8f31f51c2fac323d938434db67709 Mon Sep 17 00:00:00 2001 From: edisondeng Date: Fri, 31 Jul 2026 18:05:23 +0800 Subject: [PATCH] refactor(app): App-per-screen navigation, remove menu_level entirely Splits the ad-hoc menu_level state machine into standalone Apps with a uniform parent/child exit convention (long-press CONFIRM -> parent): clock_app renamed to home_app (boot-default), new set_clock_app extracted from home_app's time-edit mode, new ipc_bind_app extracted from menu_app's old level-2 screen, and the sensor-type selection step merged into pair_app as its new initial state. menu_app now only holds the 4-item top-level list (language toggle inline). Also makes both menu_app and pair_app's up/down navigation cyclic (wraps at both ends). --- App/app_alarm.c | 2 +- App/app_clock.c | 204 -------------------------------------------- App/app_home.c | 103 ++++++++++++++++++++++ App/app_ipc_bind.c | 85 ++++++++++++++++++ App/app_manager.c | 4 +- App/app_manager.h | 4 +- App/app_menu.c | 182 ++++++++------------------------------- App/app_pair.c | 94 ++++++++++++++------ App/app_set_clock.c | 133 +++++++++++++++++++++++++++++ App/app_sos.c | 2 +- App/config.h | 14 +-- App/main.c | 14 +-- wristband.uvproj | 14 ++- 13 files changed, 462 insertions(+), 393 deletions(-) delete mode 100644 App/app_clock.c create mode 100644 App/app_home.c create mode 100644 App/app_ipc_bind.c create mode 100644 App/app_set_clock.c diff --git a/App/app_alarm.c b/App/app_alarm.c index 7ea3bad..63da6dc 100644 --- a/App/app_alarm.c +++ b/App/app_alarm.c @@ -175,7 +175,7 @@ static void AlarmApp_onClose(void) static EventResult AlarmApp_onEvent(SystemEvent *evt) { if (evt->key_event != KEY_EVENT_NONE) { - AppManager_SwitchToForeground(&clock_app); // 清除报警并切回时钟待机页 + AppManager_SwitchToForeground(&home_app); // 清除报警并切回时钟待机页 return EVENT_HANDLED; } return EVENT_IGNORED; diff --git a/App/app_clock.c b/App/app_clock.c deleted file mode 100644 index 3283109..0000000 --- a/App/app_clock.c +++ /dev/null @@ -1,204 +0,0 @@ -#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" -#include "../Drivers/pcf8563.h" - -// 外部引用的全局系统状态变量 -extern SystemState current_state; -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); // 开启天线并置为接收模式,监听无线探测器 - - // 尝试从 PCF8563 RTC 芯片中读取硬件时间 (具备非阻塞防卡死保护) - if (PCF8563_Init()) { - PCF8563_ReadTime(¤t_hour, ¤t_min, ¤t_sec); - XTELL_LOG("[ClockApp] RTC Hardware Synced OK!\r\n"); - } else { - XTELL_LOG("[ClockApp] RTC Hardware Offline, using system clock.\r\n"); - } - - // 绘制并渲染待机时钟主页面画面 - 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); // 熄灭蓝色指示灯 - - // 同步将最新时间 (hour, min, 0秒) 写入 PCF8563 芯片寄存器 - PCF8563_WriteTime(current_hour, current_min, 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; // 其他未被关心的事件交由后台应用或丢弃 -} diff --git a/App/app_home.c b/App/app_home.c new file mode 100644 index 0000000..4b5c69a --- /dev/null +++ b/App/app_home.c @@ -0,0 +1,103 @@ +#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" +#include "../Drivers/pcf8563.h" + +// 外部引用的全局系统状态变量 +extern SystemState current_state; +extern volatile u16 ms_tick; + +// 声明本应用的生命周期回调函数 +static void HomeApp_OnStart(void); +static void HomeApp_onRun(void); +static void HomeApp_onClose(void); +static EventResult HomeApp_onEvent(SystemEvent *evt); + +// 实体化定义待机时钟主页应用控制块 (开机默认显示的首屏) +WristbandApp home_app = { + APP_ID_HOME, + "Home", + HomeApp_OnStart, + HomeApp_onRun, + HomeApp_onClose, + HomeApp_onEvent +}; + +/** + * @brief HomeApp 启动回调 + */ +static void HomeApp_OnStart(void) +{ + current_state = STATE_NORMAL; // 系统切入正常待机状态 + RF_SetMode(1); // 开启天线并置为接收模式,监听无线探测器 + + // 尝试从 PCF8563 RTC 芯片中读取硬件时间 (具备非阻塞防卡死保护) + if (PCF8563_Init()) { + PCF8563_ReadTime(¤t_hour, ¤t_min, ¤t_sec); + XTELL_LOG("[HomeApp] RTC Hardware Synced OK!\r\n"); + } else { + XTELL_LOG("[HomeApp] RTC Hardware Offline, using system clock.\r\n"); + } + + // 绘制并渲染待机时钟主页面画面 + UI_ShowClockPage(current_state, current_hour, current_min); + + // 关断/熄灭 FCOB 双灯条 + RGB_Send(0, 0, 0, 0, 0, 0); + + XTELL_LOG("[HomeApp] Started\r\n"); +} + +/** + * @brief HomeApp 轮询主回调 + */ +static void HomeApp_onRun(void) +{ + // 软时钟发生分钟改变,需要通知屏幕重画时钟主页 + if (clock_updated) { + clock_updated = 0; + UI_ShowClockPage(current_state, current_hour, current_min); + } +} + +/** + * @brief HomeApp 退出清理回调 + */ +static void HomeApp_onClose(void) +{ + RGB_Send(0, 0, 0, 0, 0, 0); // 关闭灯条 + XTELL_LOG("[HomeApp] Closed\r\n"); +} + +/** + * @brief HomeApp 接收事件处理回调 (按键导航与布撤防交互) + */ +static EventResult HomeApp_onEvent(SystemEvent *evt) +{ + // 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; // 其他未被关心的事件交由后台应用或丢弃 +} diff --git a/App/app_ipc_bind.c b/App/app_ipc_bind.c new file mode 100644 index 0000000..bd8e063 --- /dev/null +++ b/App/app_ipc_bind.c @@ -0,0 +1,85 @@ +#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 volatile u16 ms_tick; + +// 本应用私有变量 +static u16 last_bind_tx_ms = 0; // 记录上一次绑定发射的时间戳 + +// 声明本应用的生命周期回调函数 +static void IpcBindApp_OnStart(void); +static void IpcBindApp_onRun(void); +static void IpcBindApp_onClose(void); +static EventResult IpcBindApp_onEvent(SystemEvent *evt); + +// 实体化定义摄像头一键绑定应用控制块 (从 menu_app 的"LIAISON"选项进入) +WristbandApp ipc_bind_app = { + APP_ID_IPC_BIND, + "IpcBind", + IpcBindApp_OnStart, + IpcBindApp_onRun, + IpcBindApp_onClose, + IpcBindApp_onEvent +}; + +/** + * @brief IpcBindApp 启动回调 + */ +static void IpcBindApp_OnStart(void) +{ + last_bind_tx_ms = ms_tick; // 记录初始发射时间 + current_state = STATE_IPC_BIND; // 系统设为 IPC 绑定状态 + UI_ShowBindingPage(CLONED_ADDR); // 绘制相机绑定信息页 + XTELL_LOG("[IpcBindApp] Started\r\n"); +} + +/** + * @brief IpcBindApp 轮询主回调 + */ +static void IpcBindApp_onRun(void) +{ + // ====== 控制青色双灯珠同步呼吸效果 (呼吸周期 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("[IpcBindApp] Binding frame emitted\r\n"); + } +} + +/** + * @brief IpcBindApp 退出清理回调 + */ +static void IpcBindApp_onClose(void) +{ + RGB_Send(0, 0, 0, 0, 0, 0); // 熄灭 RGB + XTELL_LOG("[IpcBindApp] Closed\r\n"); +} + +/** + * @brief IpcBindApp 接收按键事件处理回调 + */ +static EventResult IpcBindApp_onEvent(SystemEvent *evt) +{ + // 长按 ■ 确认键,取消绑定,退出发射并返回主菜单 (父 App) + if (evt->key_event == KEY_EVENT_CONFIRM_LONG) { + current_state = STATE_PAIR_MENU; + AppManager_SwitchToForeground(&menu_app); + return EVENT_HANDLED; + } + return EVENT_IGNORED; +} diff --git a/App/app_manager.c b/App/app_manager.c index f120456..a59bf62 100644 --- a/App/app_manager.c +++ b/App/app_manager.c @@ -180,9 +180,11 @@ u8 AppManager_GetActiveAppID(void) // 供遗留代码兼容性引用的 API:StartApp 映射至 SwitchToForeground 动态方式 void AppManager_StartApp(AppID app_id) { - if (app_id == APP_ID_CLOCK) AppManager_SwitchToForeground(&clock_app); + if (app_id == APP_ID_HOME) AppManager_SwitchToForeground(&home_app); else if (app_id == APP_ID_MENU) AppManager_SwitchToForeground(&menu_app); + else if (app_id == APP_ID_SET_CLOCK) AppManager_SwitchToForeground(&set_clock_app); else if (app_id == APP_ID_PAIR) AppManager_SwitchToForeground(&pair_app); + else if (app_id == APP_ID_IPC_BIND) AppManager_SwitchToForeground(&ipc_bind_app); else if (app_id == APP_ID_ALARM) AppManager_SwitchToForeground(&alarm_app); else if (app_id == APP_ID_SOS) AppManager_SwitchToForeground(&sos_app); } diff --git a/App/app_manager.h b/App/app_manager.h index a68c48b..8b4e8c1 100644 --- a/App/app_manager.h +++ b/App/app_manager.h @@ -67,9 +67,11 @@ u8 AppManager_GetActiveAppID(void); void AppManager_StartApp(AppID app_id); // ====== 系统中各独立 App 实例的外包声明 (各 App 内部进行实体定义) ====== -extern WristbandApp clock_app; +extern WristbandApp home_app; extern WristbandApp menu_app; +extern WristbandApp set_clock_app; extern WristbandApp pair_app; +extern WristbandApp ipc_bind_app; extern WristbandApp alarm_app; extern WristbandApp sos_app; extern WristbandApp rf_monitor_app; // 后台射频监控应用 diff --git a/App/app_menu.c b/App/app_menu.c index 43b1289..938a049 100644 --- a/App/app_menu.c +++ b/App/app_menu.c @@ -8,40 +8,21 @@ #include "language.h" #include "../Drivers/rf.h" -// 二级传感器对码菜单中,各类传感器显示名称的字符串 ID,实际文案由 Lang_Get() 按当前语言取出 -const StringID device_menu_ids[5] = { - STR_DEV_PORTE, // 0: 门磁探测器 - STR_DEV_PIR, // 1: 红外探测器 - STR_DEV_FUMEE, // 2: 烟雾传感器 - STR_DEV_GAZ, // 3: 燃气探测器 - STR_DEV_EAU // 4: 水浸探测器 -}; - // 外部全局变量引用 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; // 记录上一次绑定发射的时间戳 - -// 引入外部时钟调节接口 -extern void Start_Time_Edit(void); // 声明本应用的生命周期回调函数 static void MenuApp_OnStart(void); -static void MenuApp_onRun(void); static void MenuApp_onClose(void); static EventResult MenuApp_onEvent(SystemEvent *evt); -// 实体化定义菜单应用控制块 +// 实体化定义菜单应用控制块 (顶层 4 项列表:语言切换/时间设置/传感器配对/摄像头绑定) WristbandApp menu_app = { APP_ID_MENU, "Menu", MenuApp_OnStart, - MenuApp_onRun, + NULL, MenuApp_onClose, MenuApp_onEvent }; @@ -51,155 +32,66 @@ WristbandApp menu_app = { */ static void MenuApp_OnStart(void) { - menu_level = 0; menu_select = 0; current_state = STATE_PAIR_MENU; // 系统运行状态设为对码配置菜单 RF_SetMode(0); // 关闭射频接收芯片,防止接收杂波干扰屏幕绘制 - UI_ShowMainMenu(menu_select); // 初始化绘制一级主菜单 + 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 接收按键与控制事件回调 + * @brief MenuApp 接收按键与控制事件回调 (0-语言切换, 1-HORLOGE, 2-APPAIRAGE, 3-LIAISON) */ static EventResult MenuApp_onEvent(SystemEvent *evt) { - if (menu_level == 0) - { - // ====== 处于一级主菜单层 (0-语言切换, 1-HORLOGE, 2-APPAIRAGE, 3-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 < 3) { - menu_select++; - UI_ShowMainMenu(menu_select); - } - return EVENT_HANDLED; - } - if (evt->key_event == KEY_EVENT_CONFIRM_CLICK) { - // 短按 ■ 确认键,进入选定功能的子页面 - if (menu_select == 0) { - // 功能 0:语言切换。原地在 FR/EN 间切换,立即保存并用新语言重绘当前菜单 - Language new_lang = (Lang_Current() == LANG_FR) ? LANG_EN : LANG_FR; - Lang_Set(new_lang); - Save_Database(); // 语言字节与传感器数据库共用同一扇区持久化 - MOTOR = 1; Delay_ms(50); MOTOR = 0; // 短震反馈,与其它确认操作风格一致 - UI_ShowMainMenu(menu_select); - } else if (menu_select == 1) { - // 功能 1:设置时间。启动待机时钟应用,并调用微调编辑入口 - AppManager_SwitchToForeground(&clock_app); - Start_Time_Edit(); - } else if (menu_select == 2) { - // 功能 2:传感器学习。进入二级传感器类别选择菜单 - menu_level = 1; - menu_select = 0; - UI_ShowPairMenuPage(menu_select); // 绘制二级设备分类页 - } else if (menu_select == 3) { - // 功能 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; - } + if (evt->key_event == KEY_EVENT_UP_CLICK) { + // 短按 ▲ 键向上移动焦点,允许循环切换 (到顶绕到底) + menu_select = (menu_select == 0) ? 3 : (menu_select - 1); + UI_ShowMainMenu(menu_select); + 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 = 2; // 回到 APPAIRAGE 选项上 - UI_ShowMainMenu(menu_select); - return EVENT_HANDLED; - } + if (evt->key_event == KEY_EVENT_DOWN_CLICK) { + // 短按 ▼ 键向下移动焦点,允许循环切换 (到底绕到顶) + menu_select = (menu_select == 3) ? 0 : (menu_select + 1); + 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 = 3; // 回到 LIAISON 选项上 - current_state = STATE_PAIR_MENU; - RGB_Send(0, 0, 0, 0, 0, 0); // 关闭呼吸灯 + if (evt->key_event == KEY_EVENT_CONFIRM_CLICK) { + // 短按 ■ 确认键,进入选定功能的子页面 + if (menu_select == 0) { + // 功能 0:语言切换。原地在 FR/EN 间切换,立即保存并用新语言重绘当前菜单 + Language new_lang = (Lang_Current() == LANG_FR) ? LANG_EN : LANG_FR; + Lang_Set(new_lang); + Save_Database(); // 语言字节与传感器数据库共用同一扇区持久化 + MOTOR = 1; Delay_ms(50); MOTOR = 0; // 短震反馈,与其它确认操作风格一致 UI_ShowMainMenu(menu_select); - return EVENT_HANDLED; + } else if (menu_select == 1) { + // 功能 1:设置时间。切换到独立的时间设置子 App + AppManager_SwitchToForeground(&set_clock_app); + } else if (menu_select == 2) { + // 功能 2:传感器学习。切换到独立的对码子 App (类型选择是它的第一个状态) + AppManager_SwitchToForeground(&pair_app); + } else if (menu_select == 3) { + // 功能 3:相机配对。切换到独立的摄像头绑定子 App + AppManager_SwitchToForeground(&ipc_bind_app); } + return EVENT_HANDLED; + } + if (evt->key_event == KEY_EVENT_CONFIRM_LONG) { + // 长按 ■ 确认键,退出设置菜单直接返回待机主页 + AppManager_SwitchToForeground(&home_app); + return EVENT_HANDLED; } return EVENT_IGNORED; } diff --git a/App/app_pair.c b/App/app_pair.c index dfba2ea..224e3ec 100644 --- a/App/app_pair.c +++ b/App/app_pair.c @@ -12,10 +12,21 @@ extern SystemState current_state; extern u8 captured_type; extern u32 captured_addr; +extern u8 menu_select; extern volatile u16 ms_tick; +// 二级传感器对码菜单中,各类传感器显示名称的字符串 ID,实际文案由 Lang_Get() 按当前语言取出 +const StringID device_menu_ids[5] = { + STR_DEV_PORTE, // 0: 门磁探测器 + STR_DEV_PIR, // 1: 红外探测器 + STR_DEV_FUMEE, // 2: 烟雾传感器 + STR_DEV_GAZ, // 3: 燃气探测器 + STR_DEV_EAU // 4: 水浸探测器 +}; + // 对码状态机定义 enum { + PAIR_STATE_SELECT_TYPE, // 选择要学习的传感器类型 (门磁/PIR/烟感/气感/水浸) PAIR_STATE_WAIT, // 等待射频探测器触发 (扩散雷达页面) PAIR_STATE_CONFIRM, // 已捕获,进入分配防区确认页面 PAIR_STATE_SUCCESS, // 对码绑定成功界面 (SUCCESS) @@ -48,13 +59,10 @@ WristbandApp pair_app = { }; /** - * @brief PairApp 启动回调 + * @brief 进入雷达搜索等待阶段 (选定传感器类型之后) */ -static void PairApp_OnStart(void) +static void PairApp_EnterWait(void) { - // 1. 为了防止后台射频监听与对码射频接收冲突,将后台射频监听应用暂时卸载 - AppManager_DetachFromBackground(&rf_monitor_app); - pair_state = PAIR_STATE_WAIT; last_radar_sec = 99; // 强制刷写 current_state = STATE_PAIR_WAIT; @@ -62,14 +70,32 @@ static void PairApp_OnStart(void) 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"); + + XTELL_LOG("[PairApp] Entering WAIT\r\n"); +} + +/** + * @brief PairApp 启动回调 + */ +static void PairApp_OnStart(void) +{ + // 为了防止后台射频监听与对码射频接收冲突,将后台射频监听应用暂时卸载 + AppManager_DetachFromBackground(&rf_monitor_app); + + pair_state = PAIR_STATE_SELECT_TYPE; + menu_select = 0; + current_state = STATE_PAIR_MENU; + RF_SetMode(0); // 选类型阶段暂不需要天线,防止接收杂波干扰屏幕绘制 + UI_ShowPairMenuPage(menu_select); // 绘制传感器类型选择页 + RGB_Send(0, 0, 0, 0, 0, 0); + + XTELL_LOG("[PairApp] Started (SELECT_TYPE)\r\n"); } /** @@ -103,7 +129,7 @@ static void PairApp_onRun(void) last_radar_sec = frame; UI_ShowPairWaitPage(frame); } - + // 累加计算 30 秒的对码时间上限 pair_pulse_timer += tick_diff; if (pair_pulse_timer >= 30000UL) { @@ -112,13 +138,13 @@ static void PairApp_onRun(void) 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) @@ -142,14 +168,14 @@ static void PairApp_onRun(void) else RGB_Send(0, 0, 0, 0, 0, 0); } else if (pair_state == PAIR_STATE_SUCCESS) { - // ====== 成功展示状态:延时 2 秒自动返回二级设置菜单 ====== + // ====== 成功展示状态:延时 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 秒自动返回二级设置菜单 ====== + // ====== 失败展示状态:延时 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); @@ -177,6 +203,30 @@ static void PairApp_onClose(void) */ static EventResult PairApp_onEvent(SystemEvent *evt) { + if (pair_state == PAIR_STATE_SELECT_TYPE) { + if (evt->key_event == KEY_EVENT_UP_CLICK) { + // 短按 ▲ 键上移焦点,允许循环切换 (到顶绕到底) + menu_select = (menu_select == 0) ? 4 : (menu_select - 1); + UI_ShowPairMenuPage(menu_select); + return EVENT_HANDLED; + } + if (evt->key_event == KEY_EVENT_DOWN_CLICK) { + // 短按 ▼ 键下移焦点,允许循环切换 (到底绕到顶) + menu_select = (menu_select == 4) ? 0 : (menu_select + 1); + UI_ShowPairMenuPage(menu_select); + return EVENT_HANDLED; + } + if (evt->key_event == KEY_EVENT_CONFIRM_CLICK) { + // 确认选择:缓存用户选好的传感器类别,短震反馈并进入雷达搜索阶段 + captured_type = menu_select; + MOTOR = 1; + Delay_ms(100); + MOTOR = 0; + PairApp_EnterWait(); + return EVENT_HANDLED; + } + } + if (pair_state == PAIR_STATE_CONFIRM) { if (evt->key_event == KEY_EVENT_UP_CLICK) { // 短按 ▲ 键增加目标分配防区号 (上限 99) @@ -197,31 +247,25 @@ static EventResult PairApp_onEvent(SystemEvent *evt) 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) { + // 在任何阶段,长按 ■ 确认键或长按 ▼ 键都代表放弃/取消对码,统一退回主菜单 (父 App) + if (evt->key_event == KEY_EVENT_CONFIRM_LONG || evt->key_event == KEY_EVENT_DOWN_LONG) { RGB_Send(0, 0, 0, 0, 0, 0); AppManager_SwitchToForeground(&menu_app); return EVENT_HANDLED; diff --git a/App/app_set_clock.c b/App/app_set_clock.c new file mode 100644 index 0000000..b655314 --- /dev/null +++ b/App/app_set_clock.c @@ -0,0 +1,133 @@ +#include "app_manager.h" +#include "clock.h" +#include "xtell.h" +#include "ui.h" +#include "rgb.h" +#include "event.h" +#include "system.h" +#include "../Drivers/pcf8563.h" + +// 外部引用的全局系统状态变量 +extern SystemState current_state; +extern volatile u16 ms_tick; + +// 本应用私有的时间编辑状态 +static u8 time_edit_field = 1; // 标识当前聚焦在哪一个参数上进行微调 (1:修改小时, 2:修改分钟) +static u8 time_edit_blink_on = 1; // 时钟修改时光标闪烁周期状态位 + +// 声明本应用的生命周期回调函数 +static void SetClockApp_OnStart(void); +static void SetClockApp_onRun(void); +static void SetClockApp_onClose(void); +static EventResult SetClockApp_onEvent(SystemEvent *evt); + +// 实体化定义时间设置应用控制块 (从 menu_app 的"HORLOGE"选项进入) +WristbandApp set_clock_app = { + APP_ID_SET_CLOCK, + "SetClock", + SetClockApp_OnStart, + SetClockApp_onRun, + SetClockApp_onClose, + SetClockApp_onEvent +}; + +/** + * @brief SetClockApp 启动回调 + */ +static void SetClockApp_OnStart(void) +{ + 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("[SetClockApp] Started\r\n"); +} + +/** + * @brief SetClockApp 轮询主回调 + */ +static void SetClockApp_onRun(void) +{ + // ====== 时间设置闪烁动画 (周期 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); + } +} + +/** + * @brief SetClockApp 退出清理回调 + */ +static void SetClockApp_onClose(void) +{ + RGB_Send(0, 0, 0, 0, 0, 0); // 关闭灯条 + XTELL_LOG("[SetClockApp] Closed\r\n"); +} + +/** + * @brief SetClockApp 接收按键事件处理回调 + */ +static EventResult SetClockApp_onEvent(SystemEvent *evt) +{ + 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) + { + // 短按 ■ 确认键保存最新时钟配置,退出到主菜单 (父 App) + current_state = STATE_PAIR_MENU; + + // 同步将最新时间 (hour, min, 0秒) 写入 PCF8563 芯片寄存器 + PCF8563_WriteTime(current_hour, current_min, 0); + + // 退出短振反馈 + MOTOR = 1; Delay_ms(50); MOTOR = 0; + + AppManager_SwitchToForeground(&menu_app); + return EVENT_HANDLED; + } + return EVENT_HANDLED; // 在时间设置模式下屏蔽拦截其他一切无关按键事件 +} diff --git a/App/app_sos.c b/App/app_sos.c index cfc4ca1..f63cf6c 100644 --- a/App/app_sos.c +++ b/App/app_sos.c @@ -127,7 +127,7 @@ static void SOSApp_onClose(void) static EventResult SOSApp_onEvent(SystemEvent *evt) { if (evt->key_event != KEY_EVENT_NONE) { - AppManager_SwitchToForeground(&clock_app); // 退出 SOS,返回时钟 + AppManager_SwitchToForeground(&home_app); // 退出 SOS,返回时钟 return EVENT_HANDLED; } return EVENT_IGNORED; diff --git a/App/config.h b/App/config.h index 5f1eb0c..007ff87 100644 --- a/App/config.h +++ b/App/config.h @@ -108,12 +108,14 @@ typedef enum { /* ====== 应用程序 ID 枚举 ====== */ typedef enum { - APP_ID_CLOCK, // 待机时钟应用程序 (首屏) - APP_ID_MENU, // 系统设置与操作主菜单应用程序 - APP_ID_PAIR, // 无线传感器对码学习应用程序 - APP_ID_ALARM, // 报警接收与波形反馈应用程序 - APP_ID_SOS, // 紧急呼救与无线发射应用程序 - APP_ID_MAX // 应用程序最大数量上限,用于数组大小 + APP_ID_HOME, // 待机时钟主页应用程序 (开机默认首屏) + APP_ID_MENU, // 系统设置主菜单应用程序 (顶层 4 项列表) + APP_ID_SET_CLOCK, // 时间设置应用程序 (menu_app 子应用) + APP_ID_PAIR, // 无线传感器对码学习应用程序 (menu_app 子应用) + APP_ID_IPC_BIND, // 摄像头一键绑定应用程序 (menu_app 子应用) + APP_ID_ALARM, // 报警接收与波形反馈应用程序 + APP_ID_SOS, // 紧急呼救与无线发射应用程序 + APP_ID_MAX // 应用程序最大数量上限,用于数组大小 } AppID; /* ====== 应用程序类型枚举 ====== */ diff --git a/App/main.c b/App/main.c index 47d6bd5..1ef7a4a 100644 --- a/App/main.c +++ b/App/main.c @@ -15,7 +15,7 @@ SystemState current_state = STATE_NORMAL; // 系统整体的当前运行状态,默认为正常时钟待机页 -u8 menu_select = 0; // 主菜单当前选中的行高亮索引 (0~2) +u8 menu_select = 0; // 当前菜单高亮的选中项索引 (menu_app 0~3,pair_app 传感器类型选择 0~4) u32 captured_addr = 0; // 配对或报警时,捕获到的传感器 24 位 EV1527 射频物理地址 u8 captured_type = 0; // 正在对码或触发警报的传感器设备类型 (0:门磁, 1:PIR...) u8 alarm_sensor_slot = 0; // 触发警报的传感器在 sensor_list 数组中的索引槽位号 @@ -162,21 +162,21 @@ void Debug_ProcessCommand(char cmd) } } else if (cmd == '1') { // 模拟切换至 正常撤防 (NORMAL) 状态并刷新时钟 - if (AppManager_GetActiveAppID() == APP_ID_CLOCK) { + if (AppManager_GetActiveAppID() == APP_ID_HOME) { current_state = STATE_NORMAL; UI_ShowClockPage(current_state, current_hour, current_min); } Uart_SendString("[UART] State: NORMAL\r\n"); } else if (cmd == '2') { // 模拟切换至 已布防 (ARMED) 状态,普通门磁可触发警报 - if (AppManager_GetActiveAppID() == APP_ID_CLOCK) { + if (AppManager_GetActiveAppID() == APP_ID_HOME) { current_state = STATE_ARMED; UI_ShowClockPage(current_state, current_hour, current_min); } Uart_SendString("[UART] State: ARMED\r\n"); } else if (cmd == '3') { // 模拟切换至 已撤防 (DISARMED) 状态,忽略普通报警 - if (AppManager_GetActiveAppID() == APP_ID_CLOCK) { + if (AppManager_GetActiveAppID() == APP_ID_HOME) { current_state = STATE_DISARMED; UI_ShowClockPage(current_state, current_hour, current_min); } @@ -267,7 +267,7 @@ void main(void) /* 步骤 1: 初始化事件环形缓冲区队列 */ EventQueue_Init(); - /* 步骤 2+5: 初始化应用管理器并登记所有前台应用,默认加载 ClockApp */ + /* 步骤 2+5: 初始化应用管理器并登记所有前台应用,默认加载 HomeApp */ AppManager_Init(); /* 启动并挂载后台射频监控应用,开始侦听无线传感器信号 */ @@ -277,7 +277,7 @@ void main(void) Timer1_Init(); /* 开机自动加载并运行待机时钟前台活跃应用 */ - AppManager_StartApp(APP_ID_CLOCK); + AppManager_StartApp(APP_ID_HOME); Uart_SendString("[SYS] Event-driven framework ready\r\n"); @@ -309,7 +309,7 @@ void main(void) current_state = STATE_SLEEP; // 系统置为休眠状态 Enter_Low_Power_Sleep(); // 该函数是阻塞的,MCU 将在此挂起。唤醒后将自动从其后继续执行 current_state = STATE_NORMAL; // 唤醒后重新置为正常待机状态 - AppManager_StartApp(APP_ID_CLOCK); // 重新开启时钟应用以刷新画面和背光 + AppManager_StartApp(APP_ID_HOME); // 重新开启时钟应用以刷新画面和背光 } } } diff --git a/wristband.uvproj b/wristband.uvproj index 437f48d..833d22a 100644 --- a/wristband.uvproj +++ b/wristband.uvproj @@ -375,9 +375,14 @@ .\App\clock.c - app_clock.c + app_home.c 1 - .\App\app_clock.c + .\App\app_home.c + + + app_set_clock.c + 1 + .\App\app_set_clock.c app_menu.c @@ -389,6 +394,11 @@ 1 .\App\app_pair.c + + app_ipc_bind.c + 1 + .\App\app_ipc_bind.c + app_alarm.c 1