Files
menu/README.md
2025-12-23 09:45:36 +08:00

18 KiB
Raw Permalink Blame History

菜单组件设计文档

1. 概述

1.1 产品简介

菜单组件是一个基于状态机的轻量级菜单系统,设计用于嵌入式设备的人机交互界面。它提供了灵活的菜单节点管理、事件驱动的导航机制、权限控制和状态持久化等功能,支持多语言和模块化扩展。

1.2 设计目标

  • 轻量级:静态内存分配,无动态内存依赖,适合资源受限的嵌入式设备
  • 模块化:清晰的分层设计,各功能模块可独立开关
  • 可扩展:支持自定义状态转换、事件处理和功能扩展
  • 易使用简洁的API接口便于集成到各种应用中
  • 高可靠:完善的错误处理和状态管理

1.3 主要特性

  • 基于状态机的菜单导航
  • 静态菜单节点池管理
  • 事件驱动的交互机制
  • 权限管理系统
  • 状态持久化
  • 多语言支持
  • 内存监控
  • 模块化设计,功能可配置

2. 系统架构

2.1 分层设计

菜单组件采用分层架构设计,从下到上分为:

┌─────────────────────────────────────────────────────────────────┐
│                        应用层 (Application)                     │
├─────────────────────────────────────────────────────────────────┤
│                        API 层 (menu_api)                        │
├─────────────────────────────────────────────────────────────────┤
│  核心层 (Core)  │  权限管理  │  持久化  │  多语言  │  参数管理  │
│ ┌─────────────┐ │ ┌─────────┐ │ ┌───────┐ │ ┌───────┐ │ ┌───────┐ │
│ │ menu_core   │ │ │ menu_per│ │ │ menu_per│ │ │ menu_lang │ │ │ menu_param│ │
│ │ menu_event  │ │ │ mission │ │ │ sistence│ │ └───────┘ │ │ └───────┘ │
│ │ menu_stack  │ │ └─────────┘ │ └───────┘ │             │             │
│ │ menu_hash   │ │                         │             │             │
│ └─────────────┘ │                         │             │             │
├─────────────────────────────────────────────────────────────────┤
│                        端口层 (menu_port)                       │
├─────────────────────────────────────────────────────────────────┤
│                        硬件抽象层 (HAL)                        │
└─────────────────────────────────────────────────────────────────┘

2.2 模块关系

各模块之间的依赖关系如下:

  • API层:对外提供统一的菜单操作接口,依赖核心层和各功能模块
  • 核心层:包含菜单的核心逻辑,如节点管理、事件处理、状态机等
  • 功能模块:基于核心层扩展,提供特定功能,如权限管理、持久化等
  • 端口层:提供平台相关的接口实现,如调试打印、内存操作等

3. 核心组件设计

3.1 菜单核心 (menu_core)

3.1.1 功能描述

菜单核心是整个组件的心脏,负责:

  • 菜单节点的注册、注销和管理
  • 状态机的运行和状态转换
  • 事件的处理和分发
  • 导航路径的管理

3.1.2 核心流程

stateDiagram-v2
    [*] --> INIT: 菜单初始化
    INIT --> NORMAL: 菜单进入
    NORMAL --> PARAM_EDIT: 进入参数编辑
    PARAM_EDIT --> NORMAL: 保存参数
    PARAM_EDIT --> CONFIRM: 请求确认
    CONFIRM --> NORMAL: 确认操作
    CONFIRM --> PARAM_EDIT: 取消操作
    NORMAL --> ERROR: 发生错误
    ERROR --> NORMAL: 恢复正常
    NORMAL --> [*]: 菜单退出

3.2 事件管理 (menu_event)

3.2.1 功能描述

事件管理模块负责:

  • 事件的发布和订阅
  • 事件队列的管理
  • 事件优先级的处理

3.2.2 事件处理流程

flowchart TD
    A[事件发布] --> B{事件队列满?}
    B -->|是| C[返回错误]
    B -->|否| D[入队事件]
    D --> E[唤醒事件处理]
    E --> F{有事件?}
    F -->|是| G[出队最高优先级事件]
    G --> H[处理事件]
    H --> I{状态转换?}
    I -->|是| J[执行状态转换]
    I -->|否| K[执行事件回调]
    J --> F
    K --> F
    F -->|否| L[等待下一个事件]

3.3 节点管理 (menu_core + menu_hash)

3.3.1 功能描述

节点管理模块负责:

  • 菜单节点的注册和注销
  • 节点的查找和遍历
  • 节点关系的维护(父子、兄弟关系)
  • 节点的哈希索引

3.3.2 节点数据结构

菜单节点采用静态数组管理,每个节点包含:

  • 节点ID、父节点ID
  • 节点名称和回调函数
  • 子节点和兄弟节点的引用
  • 节点状态标志(选中、可见、启用等)
  • 权限级别(可选)
  • 参数ID可选

3.4 导航管理 (menu_stack + menu_core)

3.4.1 功能描述

导航管理模块负责:

  • 菜单的上下导航
  • 菜单的进入和退出
  • 导航栈的管理
  • 导航路径的记录

3.4.2 导航流程

flowchart TD
    A[当前节点] --> B{按下上键?}
    B -->|是| C[切换到前一个兄弟节点]
    B -->|否| D{按下下键?}
    D -->|是| E[切换到后一个兄弟节点]
    D -->|否| F{按下确认键?}
    F -->|是| G[进入子节点/执行操作]
    F -->|否| H{按下退出键?}
    H -->|是| I[返回父节点]
    H -->|否| J[处理其他事件]
    C --> K[更新当前节点]
    E --> K
    G --> K
    I --> K
    J --> K
    K --> A

4. 数据结构设计

4.1 核心数据结构

4.1.1 菜单节点 (MenuNode)

typedef struct MenuNode {
    MenuNodeId id;               ///< 节点ID唯一
    MenuNodeId parent_id;        ///< 父节点ID根节点为0
    const char* name;            ///< 菜单名称(或多语言索引)
    MenuCallback enter_cb;       ///< 进入回调
    MenuCallback exit_cb;        ///< 退出回调
    MenuNodeId first_child_id;   ///< 第一个子节点ID
    MenuNodeId next_sibling_id;  ///< 下一个兄弟节点ID
    MenuNodeId prev_sibling_id;  ///< 上一个兄弟节点ID
    MenuNodeId hash_next_id;     ///< 哈希表下一个节点ID
    struct {
        bool is_registered : 1;  ///< 是否已注册
        bool is_selected : 1;    ///< 是否被选中
        bool is_visible : 1;     ///< 是否可见
        bool is_enabled : 1;     ///< 是否启用
        unsigned int reserved : 4; ///< 保留位
    } flags;
    MenuPermissionLevel permission_level; ///< 权限级别
    uint16_t param_id;           ///< 绑定的参数ID
} MenuNode;

4.1.2 菜单核心上下文 (MenuCoreCtx)

typedef struct MenuCoreCtx {
    MenuNode nodes[MENU_CONFIG_MAX_NODES];  ///< 静态菜单节点池
    MenuNodeId hash_table[MENU_CONFIG_HASH_TABLE_SIZE];  ///< 哈希表
    MenuStack stack;                        ///< 菜单导航栈
    MenuNavPath nav_path;                   ///< 菜单导航路径
    MenuEventQueue event_queue;             ///< 事件队列
    MenuNodeId current_node_id;             ///< 当前选中的节点ID
    MenuState current_state;                ///< 当前菜单状态
    uint32_t last_refresh_tick;             ///< 上次刷新时间
    bool is_initialized;                    ///< 是否已初始化
    MenuErrCode error_code;                 ///< 当前错误码
    const char* error_msg;                  ///< 当前错误信息
    uint16_t node_count;                    ///< 已注册节点数量
    uint16_t free_node_count;               ///< 空闲节点数量
    // 功能模块扩展字段...
} MenuCoreCtx;

4.2 状态和事件定义

状态名称 状态值 描述
MENU_STATE_INIT 0 初始化状态
MENU_STATE_NORMAL 1 正常导航状态
MENU_STATE_PARAM_EDIT 2 参数编辑状态
MENU_STATE_CONFIRM 3 确认状态
MENU_STATE_ERROR 4 错误状态
事件名称 事件值 描述
MENU_EVENT_NONE 0 无事件
MENU_EVENT_KEY_UP 1 上键事件
MENU_EVENT_KEY_DOWN 2 下键事件
MENU_EVENT_KEY_ENTER 3 确认键事件
MENU_EVENT_KEY_ESC 4 退出键事件
MENU_EVENT_TIMEOUT 5 超时事件
MENU_EVENT_CUSTOM_BASE 10 自定义事件基值

5. 接口设计

5.1 API 概述

菜单组件提供了简洁的API接口主要分为以下几类

API类别 主要功能 文件位置
初始化与反初始化 组件的初始化和反初始化 menu_api.h
节点管理 节点的注册、注销和更新 menu_api.h
事件处理 事件的发布和处理 menu_api.h
导航操作 菜单的上下导航、进入退出 menu_api.h
状态查询 获取当前状态、节点和路径 menu_api.h
权限管理 角色注册、权限检查 menu_api.h
持久化 状态的保存和恢复 menu_api.h

5.2 核心API示例

5.2.1 初始化菜单

MenuErrCode ret = menu_init();
if (ret != MENU_ERR_OK) {
    // 处理初始化错误
}

5.2.2 注册菜单节点

// 注册单个节点
MenuNodeId node_id = menu_register_node(
    0,                      // 自动分配节点ID
    parent_node_id,         // 父节点ID
    "Menu Name",            // 节点名称
    enter_callback,         // 进入回调
    exit_callback           // 退出回调
);

// 批量注册节点
const struct MenuNode nodes[] = {
    {1, 0, "Main Menu", main_enter, main_exit},
    {2, 1, "Sub Menu 1", sub1_enter, sub1_exit},
    {3, 1, "Sub Menu 2", sub2_enter, sub2_exit},
};
menu_register_nodes(nodes, sizeof(nodes)/sizeof(nodes[0]));

5.2.3 事件处理

// 发布事件 - 带优先级
menu_post_event(MENU_EVENT_KEY_UP, 0, MENU_EVENT_PRIORITY_NORMAL);

// 发布事件 - 简化方式(使用正常优先级)
menu_post_event_normal(MENU_EVENT_KEY_UP, 0);

// 主循环
while (1) {
    uint32_t tick = get_system_tick();
    menu_main_loop(tick);
    // 其他任务...
}

6. 功能特性

6.1 权限管理

6.1.1 功能描述

权限管理模块允许:

  • 注册不同的角色和权限级别
  • 为菜单节点设置访问权限
  • 检查当前角色是否有权访问特定节点
  • 动态切换角色

6.1.2 权限检查流程

flowchart TD
    A[访问节点请求] --> B{权限功能启用?}
    B -->|否| C[允许访问]
    B -->|是| D[获取当前角色]
    D --> E[获取节点权限级别]
    E --> F{角色权限 >= 节点权限?}
    F -->|是| C
    F -->|否| G[拒绝访问]

6.2 状态持久化

6.2.1 功能描述

状态持久化模块允许:

  • 保存当前菜单状态到非易失性存储
  • 恢复上次保存的菜单状态
  • 支持自动保存和手动保存
  • 可配置的保存间隔

6.2.2 持久化数据结构

typedef struct {
    MenuNodeId current_node_id;    ///< 当前节点ID
    MenuState current_state;       ///< 当前菜单状态
    MenuNavPath nav_path;          ///< 当前导航路径
    MenuStack stack;               ///< 当前菜单栈
    uint32_t timestamp;            ///< 持久化时间戳
    uint8_t checksum;              ///< 数据校验和
} MenuPersistenceData;

6.3 多语言支持

6.3.1 功能描述

多语言支持模块允许:

  • 注册不同语言的字符串
  • 切换当前语言
  • 根据当前语言获取对应的字符串
  • 支持动态加载语言包

6.4 内存监控

6.4.1 功能描述

内存监控模块允许:

  • 获取已使用和空闲的节点数量
  • 获取最大节点数量
  • 监控内存使用情况

7. 配置与定制

7.1 配置选项

菜单组件提供了丰富的配置选项,通过修改 menu_config.h 文件可以定制:

配置项 描述 默认值
MENU_CONFIG_MAX_NODES 最大菜单节点数 32
MENU_CONFIG_STACK_DEPTH 菜单栈深度 8
MENU_CONFIG_EVENT_QUEUE_LEN 事件队列长度 16
MENU_CONFIG_ENABLE_PERMISSION 启用权限管理 1
MENU_CONFIG_ENABLE_PERSISTENCE 启用状态持久化 1
MENU_CONFIG_ENABLE_LANG 启用多语言支持 1
MENU_CONFIG_ENABLE_DEBUG 启用调试打印 1

7.2 功能模块开关

通过配置宏可以启用或禁用特定功能模块:

// 启用权限管理
#define MENU_CONFIG_ENABLE_PERMISSION    1U

// 禁用多语言支持
#define MENU_CONFIG_ENABLE_LANG          0U

8. 示例用法

8.1 基础菜单示例

#include "menu_api.h"

// 回调函数
MenuErrCode main_enter(MenuNodeId node_id, struct MenuCoreCtx* core_ctx) {
    printf("Enter Main Menu\n");
    return MENU_ERR_OK;
}

MenuErrCode main_exit(MenuNodeId node_id, struct MenuCoreCtx* core_ctx) {
    printf("Exit Main Menu\n");
    return MENU_ERR_OK;
}

// 初始化菜单
void menu_example_init(void) {
    // 初始化菜单组件
    menu_init();
    
    // 注册菜单节点
    MenuNodeId main_node = menu_register_node(0, 0, "Main Menu", main_enter, main_exit);
    MenuNodeId sub1_node = menu_register_node(0, main_node, "Sub Menu 1", NULL, NULL);
    MenuNodeId sub2_node = menu_register_node(0, main_node, "Sub Menu 2", NULL, NULL);
    
    // 进入菜单
    menu_enter();
}

// 主循环
void menu_example_loop(uint32_t tick) {
    menu_main_loop(tick);
}

8.2 带权限管理的示例

#include "menu_api.h"

void permission_example(void) {
    // 初始化菜单
    menu_init();
    
    // 注册角色
    const MenuRole roles[] = {
        {0, "Guest", 1},     // 访客角色权限级别1
        {1, "User", 5},      // 用户角色权限级别5
        {2, "Admin", 10},    // 管理员角色权限级别10
    };
    
    for (int i = 0; i < sizeof(roles)/sizeof(roles[0]); i++) {
        menu_permission_register_role(&roles[i]);
    }
    
    // 设置当前角色
    menu_permission_set_current_role(0); // 初始为访客角色
    
    // 注册菜单节点,设置不同权限级别
    MenuNodeId main_node = menu_register_node(0, 0, "Main", NULL, NULL);
    MenuNodeId public_node = menu_register_node(0, main_node, "Public", NULL, NULL);
    MenuNodeId user_node = menu_register_node(0, main_node, "User", NULL, NULL);
    MenuNodeId admin_node = menu_register_node(0, main_node, "Admin", NULL, NULL);
    
    // 设置节点权限级别
    menu_permission_update_node_level(public_node, 1);  // 所有人可访问
    menu_permission_update_node_level(user_node, 5);    // 用户及以上可访问
    menu_permission_update_node_level(admin_node, 10);  // 仅管理员可访问
    
    // 检查权限
    bool can_access = menu_permission_check_node_access(admin_node); // 返回false当前是访客角色
    
    // 切换角色
    menu_permission_set_current_role(2); // 切换到管理员角色
    can_access = menu_permission_check_node_access(admin_node); // 返回true
}

9. 目录结构

menu/
├── api/                   # API接口层
│   ├── menu_api.h         # 对外API头文件
│   └── menu_api.c         # API实现
├── src/                   # 源代码层
│   ├── core/              # 核心模块
│   │   ├── menu_core.h/c  # 核心逻辑
│   │   ├── menu_event.h/c # 事件处理
│   │   ├── menu_stack.h/c # 栈管理
│   │   ├── menu_hash.h/c  # 哈希表
│   │   ├── menu_types.h   # 类型定义
│   │   └── menu_config.h  # 配置文件
│   ├── param/             # 参数管理
│   │   ├── menu_param.h/c
│   └── lang/              # 多语言支持
│       ├── menu_lang.h/c
├── port/                  # 端口层
│   ├── menu_port.h/c
├── examples/              # 示例代码
│   ├── language/          # 多语言示例
│   └── hmi/               # HMI示例
├── demo/                  # 演示程序
│   ├── demo.c
│   └── CMakeLists.txt
├── test/                  # 测试代码
│   ├── menu_test.c
│   └── CMakeLists.txt
├── CMakeLists.txt         # CMake构建文件
└── README.md              # 设计文档

10. 构建与集成

10.1 构建方式

菜单组件使用CMake进行构建支持多种编译器和平台

mkdir build
cd build
cmake ..
make

10.2 集成到应用

  1. 将菜单组件的源文件添加到应用项目中
  2. 包含必要的头文件(主要是 menu_api.h
  3. 根据需要配置 menu_config.h
  4. 调用API初始化和使用菜单组件

11. 版本历史

版本号 发布日期 主要变更
1.0.0 2025-12-19 初始版本
1.0.1 2025-12-20 修复了节点查找的bug优化了事件处理流程

12. 总结

菜单组件是一个功能丰富、易于扩展的轻量级菜单系统适合各种嵌入式设备的人机交互需求。它的模块化设计允许根据项目需求定制功能静态内存分配使其适合资源受限的环境完善的API设计使其易于集成到各种应用中。

通过合理使用菜单组件,可以快速构建出高效、可靠的嵌入式菜单界面,提高设备的用户体验和可维护性。