411 lines
18 KiB
Markdown
411 lines
18 KiB
Markdown
# 模块拆分 - 应用执行层 (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/马达状态输出)
|
||
}
|
||
```
|