Files
2026-07-15 15:57:37 +08:00

411 lines
18 KiB
Markdown
Raw Permalink 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.

# 模块拆分 - 应用执行层 (Docs/50_module-breakdown/mod-app.md)
本模块描述手环的 App 调度管理器AppManager、前台/后台应用架构、生命周期钩子OnStart, onRun, onClose, onEvent、事件分发机制、CPU占用控制以及各功能模块的页面渲染与状态机调度设计。
---
## 1. 模块职责说明
### 1.1 核心职责
| 职责领域 | 描述 |
| :--- | :--- |
| **应用生命周期管理** | 维护应用注册表,管理应用的启动、切换、关闭流程 |
| **前台/后台应用分离** | 支持前台应用独占屏幕和后台应用持续监听的双轨运行模式 |
| **事件分发路由** | 实现前台优先、后台兜底、未处理丢弃的事件分发策略 |
| **CPU占用控制** | 监控应用 onRun() 执行时间防止单个应用长时间占用CPU |
| **应用状态维护** | 维护活跃应用指针、应用运行状态标记等核心数据结构 |
### 1.2 应用类型划分
系统将应用分为两大类,通过 `AppType` 枚举区分:
| 应用类型 | 标识 | 特性 | 生命周期 |
| :--- | :--- | :--- | :--- |
| **前台应用** | `APP_TYPE_FOREGROUND` | 独占屏幕显示,响应用户交互,一次只有一个活跃 | `OnStart``onRun`(循环) → `onClose` |
| **后台应用** | `APP_TYPE_BACKGROUND` | 无屏幕显示,持续运行监听特定事件,可多个同时运行 | 注册后自动运行,无显式启动/关闭 |
### 1.3 应用列表
| 应用ID | 应用名称 | 类型 | 职责描述 |
| :--- | :--- | :--- | :--- |
| `APP_ID_CLOCK` | ClockApp | 前台 | 待机时钟应用,中央以 32x64px 超大点阵渲染当前时分,右上角渲染 16x32px 电量百分比及充电指示;后台监听射频报警信号 |
| `APP_ID_MENU` | MenuApp | 前台 | 主菜单应用,单屏单条目显示交互,支持 ▲/▼ 键高亮切换,确认键进入对应子页面 |
| `APP_ID_PAIR` | PairApp | 前台 | 配对与微调应用,对码捕捉到未配对 24 位射频码时拉起,提供微调序号选框,确认后存入持久化 Flash |
| `APP_ID_ALARM` | AlarmApp | 前台 | 传感器报警应用,被后台监测唤醒拉起,屏幕闪烁红色粗边框,调度马达振动波形与 LED 警示颜色 |
| `APP_ID_SOS` | SOSApp | 前台 | 主动求救应用,短按/长按 SOS 物理按键拉起,周期性发射 0x08 (SOS) 无线电数据,触发最高优先级的持续马达震动及红色幻彩灯爆闪 |
---
## 2. 核心数据结构
### 2.1 应用描述符结构 (WristbandApp)
```c
typedef struct {
AppID app_id; // 应用唯一标识 (APP_ID_CLOCK/MENU/PAIR/ALARM/SOS)
AppType app_type; // 应用类型 (APP_TYPE_FOREGROUND / APP_TYPE_BACKGROUND)
char *app_name; // 应用名称(用于调试日志输出)
// 生命周期回调函数指针
void (*OnStart)(void); // 启动回调:应用切入前台,加载资源、初始化显示
void (*onRun)(void); // 循环回调:主循环轮询调用,执行非阻塞业务逻辑
void (*onClose)(void); // 关闭回调:应用退出前台,保存状态、清理外设
EventResult (*onEvent)(SystemEvent evt); // 事件回调:接收并处理系统事件
// 运行状态标记
bit is_active; // 当前是否活跃(仅对前台应用有效,标识是否在前台运行)
bit is_running; // 应用是否处于运行状态用于CPU占用控制标记是否允许调用onRun
} WristbandApp;
```
### 2.2 应用ID枚举
```c
typedef enum {
APP_ID_CLOCK, // 0: 待机时钟应用
APP_ID_MENU, // 1: 主菜单应用
APP_ID_PAIR, // 2: 配对与微调应用
APP_ID_ALARM, // 3: 传感器报警应用
APP_ID_SOS, // 4: 主动求救应用
APP_ID_MAX // 5: 应用数量上限(用于数组大小)
} AppID;
```
### 2.3 应用类型枚举
```c
typedef enum {
APP_TYPE_FOREGROUND, // 前台应用:独占屏幕和用户交互
APP_TYPE_BACKGROUND // 后台应用:无屏幕显示,仅监听事件
} AppType;
```
### 2.4 事件处理返回值
```c
typedef enum {
EVENT_HANDLED, // 事件已处理,停止继续分发
EVENT_IGNORED // 事件未处理,继续传递给下一个应用
} EventResult;
```
---
## 3. 应用管理器核心接口
### 3.1 初始化与注册接口
| 接口名称 | 函数签名 | 功能描述 |
| :--- | :--- | :--- |
| `AppManager_Init` | `void AppManager_Init(void)` | 初始化应用管理器,初始化应用注册表,设置默认活跃应用为 ClockApp |
| `AppManager_RegisterApp` | `void AppManager_RegisterApp(AppID app_id, AppType app_type, char *app_name, AppStartFunc on_start, AppRunFunc on_run, AppCloseFunc on_close, AppEventFunc on_event)` | 注册应用到注册表,绑定生命周期回调函数 |
### 3.2 应用切换接口
| 接口名称 | 函数签名 | 功能描述 |
| :--- | :--- | :--- |
| `AppManager_StartApp` | `void AppManager_StartApp(AppID app_id)` | 启动指定应用:关闭当前活跃应用 → 切换活跃应用指针 → 启动新应用 |
| `AppManager_CloseApp` | `void AppManager_CloseApp(void)` | 关闭当前活跃应用回退到默认应用ClockApp |
| `AppManager_GetActiveAppID` | `AppID AppManager_GetActiveAppID(void)` | 获取当前活跃应用的 ID |
### 3.3 事件分发接口
| 接口名称 | 函数签名 | 功能描述 |
| :--- | :--- | :--- |
| `AppManager_DispatchEvent` | `void AppManager_DispatchEvent(SystemEvent evt)` | 分发事件到应用层:前台优先 → 后台兜底 → 未处理丢弃 |
| `AppManager_RunActiveApp` | `void AppManager_RunActiveApp(void)` | 运行当前活跃应用的 onRun 回调(带 CPU 占用控制) |
---
## 4. 应用生命周期调度机制
### 4.1 生命周期状态转换
```mermaid
graph LR
subgraph 前台应用状态
Idle["未注册 / 空闲"] -->|注册| Registered["已注册"]
Registered -->|StartApp| Active["活跃中 (OnStart)"]
Active -->|onRun循环| Running["运行中"]
Running -->|StartApp(其他)| Closing["关闭中 (onClose)"]
Closing -->|切换完成| Active
Running -->|CloseApp| Closing
Closing -->|回退完成| Default["默认状态 (ClockApp)"]
end
subgraph 后台应用状态
BG_Idle["未注册"] -->|注册| BG_Running["持续运行"]
BG_Running -->|接收事件| BG_Handling["事件处理中"]
BG_Handling -->|处理完成| BG_Running
end
```
### 4.2 生命周期回调语义
| 回调函数 | 调用时机 | 职责要求 |
| :--- | :--- | :--- |
| `OnStart()` | 应用切入前台时 | 初始化显示、加载资源、重置状态变量 |
| `onRun()` | 主循环每轮调用 | 执行非阻塞业务逻辑(如动画更新、超时检测),必须控制执行时间 |
| `onClose()` | 应用退出前台时 | 保存状态、清理外设(关屏、停振、关闭射频等) |
| `onEvent(evt)` | 收到事件时 | 处理事件,返回 `EVENT_HANDLED``EVENT_IGNORED` |
### 4.3 应用切换流程
```
AppManager_StartApp(target_app_id) 执行流程:
1. 参数有效性检查
├── app_id >= APP_ID_MAX → 返回错误
├── 目标应用非前台应用 → 返回错误
└── 目标应用未注册 (OnStart == NULL) → 返回错误
2. 目标应用已是活跃应用 → 直接返回
3. 关闭当前活跃应用
└── active_app->onClose() → 清理外设、保存状态
4. 切换活跃应用指针
└── active_app = &app_registry[target_app_id]
5. 启动新应用
└── active_app->OnStart() → 初始化显示、加载资源
```
---
## 5. 事件分发机制
### 5.1 事件分发规则
事件在应用层的分发遵循以下顺序,前台应用优先处理,后台应用作为兜底:
```
事件分发流程 (AppManager_DispatchEvent):
┌──────────────────────────────────────────────────────────────┐
│ Step 1: 前台应用优先处理 │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ if (active_app != NULL && active_app->is_active) │ │
│ │ { │ │
│ │ result = active_app->onEvent(evt); │ │
│ │ if (result == EVENT_HANDLED) │ │
│ │ return; // 前台应用已处理,结束分发 │ │
│ │ } │ │
│ └────────────────────────────────────────────────────────┘ │
├──────────────────────────────────────────────────────────────┤
│ Step 2: 后台应用兜底处理 │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ for (i = 0; i < APP_ID_MAX; i++) │ │
│ │ { │ │
│ │ bg_app = &app_registry[i]; │ │
│ │ // 仅处理后台应用 │ │
│ │ if (bg_app->app_type != APP_TYPE_BACKGROUND || │ │
│ │ bg_app->onEvent == NULL) │ │
│ │ continue; │ │
│ │ │ │
│ │ result = bg_app->onEvent(evt); │ │
│ │ if (result == EVENT_HANDLED) │ │
│ │ return; // 某后台应用已处理,结束分发 │ │
│ │ } │ │
│ └────────────────────────────────────────────────────────┘ │
├──────────────────────────────────────────────────────────────┤
│ Step 3: 事件丢弃 │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ // 前台和后台应用均未处理,事件被丢弃 │ │
│ └────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────┘
```
### 5.2 事件处理示例
以下为各应用的典型事件处理行为:
| 事件类型 | ClockApp | MenuApp | PairApp | AlarmApp | SOSApp |
| :--- | :--- | :--- | :--- | :--- | :--- |
| `KEY_EVENT_UP_CLICK` | IGNORED | HANDLED (上移) | IGNORED | HANDLED (清除) | IGNORED |
| `KEY_EVENT_DOWN_CLICK` | IGNORED | HANDLED (下移) | IGNORED | HANDLED (清除) | IGNORED |
| `KEY_EVENT_SOS_CLICK` | HANDLED (启动SOS) | IGNORED | IGNORED | HANDLED (清除) | IGNORED |
| `KEY_EVENT_UP_DOWN_COMB` | HANDLED (启动Menu) | IGNORED | IGNORED | IGNORED | IGNORED |
| `KEY_EVENT_SOS_LONG` | HANDLED (绑定IPC) | IGNORED | HANDLED (保存) | IGNORED | HANDLED (退出) |
| `KEY_EVENT_DOWN_LONG` | IGNORED | HANDLED (返回) | HANDLED (退出) | IGNORED | HANDLED (退出) |
---
## 6. CPU 占用控制机制
### 6.1 设计目的
为防止单个应用的 `onRun()` 回调长时间占用 CPU 导致系统响应迟钝或卡死,框架引入 **执行时间监控机制**
### 6.2 控制参数
| 参数名称 | 定义 | 默认值 | 说明 |
| :--- | :--- | :--- | :--- |
| `APP_ONRUN_MAX_TICKS` | onRun 最大执行时间阈值 | 5 (ms) | 超过此时间则暂停应用运行 |
### 6.3 监控机制
```
AppManager_RunActiveApp() 执行流程:
1. 检查应用状态
└── if (active_app == NULL || !active_app->is_active) → 直接返回
2. 记录开始时间
└── start_tick = ms_tick
3. 执行应用的 onRun() 回调
└── active_app->onRun()
4. 计算执行耗时
└── elapsed_ticks = ms_tick - start_tick
5. 判断是否超时
├── if (elapsed_ticks > APP_ONRUN_MAX_TICKS)
│ └── active_app->is_running = 0 // 暂停应用
└── else
└── active_app->is_running = 1 // 继续运行
```
### 6.4 超时恢复
* 超时后应用的 `is_running` 标记被置为 0下一次主循环将跳过该应用的 `onRun()` 调用
* 应用可在下一次循环中自动恢复运行(`is_running``onRun` 调用前被检查,调用后被更新)
* 此机制不会杀死应用,仅暂停其循环执行,允许应用在后续恢复
---
## 7. 应用实现细节
### 7.1 ClockApp时钟应用
**职责**:待机时钟显示、后台射频监测
**生命周期实现**
| 回调 | 实现逻辑 |
| :--- | :--- |
| `OnStart` | 初始化状态为 NORMAL设置重绘标志 |
| `onRun` | 分钟变化时重绘时钟;后台监听射频信号,匹配到已配对传感器触发报警 |
| `onClose` | 清理资源 |
| `onEvent` | 处理组合键进入菜单、SOS 按键进入求救模式、长按绑定 IPC |
### 7.2 MenuApp菜单应用
**职责**:主菜单导航
**生命周期实现**
| 回调 | 实现逻辑 |
| :--- | :--- |
| `OnStart` | 初始化菜单选择项为 0关闭射频显示菜单页面 |
| `onRun` | 无持续运行逻辑 |
| `onClose` | 重新开启射频接收 |
| `onEvent` | 处理上/下键切换菜单项、确认键进入配对、长按返回时钟 |
### 7.3 PairApp配对应用
**职责**:传感器配对与保存
**生命周期实现**
| 回调 | 实现逻辑 |
| :--- | :--- |
| `OnStart` | 进入等待状态,开启射频接收,显示雷达搜索页面 |
| `onRun` | 更新雷达动画;监听射频信号捕获对码;检测超时 |
| `onClose` | 关闭射频,重置配对状态 |
| `onEvent` | 处理长按退出、确认保存对码 |
### 7.4 AlarmApp报警应用
**职责**:入侵警报显示与响应
**生命周期实现**
| 回调 | 实现逻辑 |
| :--- | :--- |
| `OnStart` | 设置状态为 ALARM显示报警页面重置报警定时器 |
| `onRun` | 控制报警边框闪烁效果 |
| `onClose` | 停止报警效果马达、LED恢复正常状态 |
| `onEvent` | 任何按键清除报警 |
### 7.5 SOSApp求救应用
**职责**:主动求救模式
**生命周期实现**
| 回调 | 实现逻辑 |
| :--- | :--- |
| `OnStart` | 设置状态为 SOS_EMITTED显示 SOS 页面 |
| `onRun` | 每 500ms 发射一次射频求救帧;控制红色边框闪烁 |
| `onClose` | 停止求救效果马达、LED恢复正常状态 |
| `onEvent` | 长按任意键解除求救 |
---
## 8. 页面渲染接口
| 接口名称 | 函数签名 | 所属应用 | 功能描述 |
| :--- | :--- | :--- | :--- |
| `UI_ShowClockPage` | `void UI_ShowClockPage(SystemState state, u8 hour, u8 min)` | ClockApp | 绘制时钟页面,包含时间显示和电量指示 |
| `UI_ShowPairMenuPage` | `void UI_ShowPairMenuPage(u8 selected_item)` | MenuApp | 绘制配对菜单页面,显示选中项图标和名称 |
| `UI_ShowPairWaitPage` | `void UI_ShowPairWaitPage(u8 frame)` | PairApp | 绘制雷达搜索页面,包含动画帧 |
| `UI_ShowPairConfirmPage` | `void UI_ShowPairConfirmPage(u32 addr)` | PairApp | 绘制配对确认页面,显示捕获到的射频地址 |
| `UI_ShowPairSuccessPage` | `void UI_ShowPairSuccessPage(u32 addr)` | PairApp | 绘制配对成功页面 |
| `UI_ShowPairFailPage` | `void UI_ShowPairFailPage(u8 reason)` | PairApp | 绘制配对失败页面 |
| `UI_ShowAlarmPage` | `void UI_ShowAlarmPage(char *name, u8 severity)` | AlarmApp | 绘制报警页面,包含红色边框和传感器名称 |
| `UI_ShowSosPage` | `void UI_ShowSosPage(void)` | SOSApp | 绘制 SOS 求救页面 |
| `UI_ShowBindingPage` | `void UI_ShowBindingPage(void)` | ClockApp | 绘制 IPC 绑定页面 |
---
## 9. 应用注册表结构
应用管理器维护一个静态数组 `app_registry[APP_ID_MAX]`,存储所有已注册的应用:
```c
static WristbandApp app_registry[APP_ID_MAX];
static WristbandApp *active_app = NULL; // 当前活跃前台应用指针
#define DEFAULT_APP_ID APP_ID_CLOCK // 默认应用为时钟
```
注册流程:
1. 系统初始化时调用 `AppManager_Init()` 初始化注册表
2. 各应用模块调用 `AppManager_RegisterApp()` 注册自身
3. 最后调用 `AppManager_Init()` 设置默认活跃应用
---
## 10. 主循环集成
主循环中应用管理器的调用顺序:
```
while(1)
{
// 1. 处理串口调试输入
cmd = Uart_RxChar();
if (cmd != '\0')
// 解析命令,生成事件,调用 EventQueue_Push 或 EventQueue_InsertFront
// 2. 处理按键事件缓冲区
if (key_event_buf != KEY_EVENT_NONE)
// 生成事件,根据优先级选择入队(Push)或插队(InsertFront)
key_event_buf = KEY_EVENT_NONE;
// 3. 处理事件队列
while (!EventQueue_IsEmpty())
{
evt = EventQueue_Pop();
AppManager_DispatchEvent(evt);
}
// 4. 运行当前活跃应用带CPU占用控制
AppManager_RunActiveApp();
// 5. 其他系统监控如LED/马达状态输出)
}
```