初始化整定
This commit is contained in:
89
.gitignore
vendored
89
.gitignore
vendored
@ -1,89 +0,0 @@
|
||||
# ---> C
|
||||
# Prerequisites
|
||||
*.d
|
||||
|
||||
# Object files
|
||||
*.o
|
||||
*.ko
|
||||
*.obj
|
||||
*.elf
|
||||
|
||||
# Linker output
|
||||
*.ilk
|
||||
*.map
|
||||
*.exp
|
||||
|
||||
# Precompiled Headers
|
||||
*.gch
|
||||
*.pch
|
||||
|
||||
# Libraries
|
||||
*.lib
|
||||
*.a
|
||||
*.la
|
||||
*.lo
|
||||
|
||||
# Shared objects (inc. Windows DLLs)
|
||||
*.dll
|
||||
*.so
|
||||
*.so.*
|
||||
*.dylib
|
||||
|
||||
# Executables
|
||||
*.exe
|
||||
*.out
|
||||
*.app
|
||||
*.i*86
|
||||
*.x86_64
|
||||
*.hex
|
||||
|
||||
# Debug files
|
||||
*.dSYM/
|
||||
*.su
|
||||
*.idb
|
||||
*.pdb
|
||||
|
||||
# Kernel Module Compile Results
|
||||
*.mod*
|
||||
*.cmd
|
||||
.tmp_versions/
|
||||
modules.order
|
||||
Module.symvers
|
||||
Mkfile.old
|
||||
dkms.conf
|
||||
|
||||
# ---> C++
|
||||
# Prerequisites
|
||||
*.d
|
||||
|
||||
# Compiled Object files
|
||||
*.slo
|
||||
*.lo
|
||||
*.o
|
||||
*.obj
|
||||
|
||||
# Precompiled Headers
|
||||
*.gch
|
||||
*.pch
|
||||
|
||||
# Compiled Dynamic libraries
|
||||
*.so
|
||||
*.dylib
|
||||
*.dll
|
||||
|
||||
# Fortran module files
|
||||
*.mod
|
||||
*.smod
|
||||
|
||||
# Compiled Static libraries
|
||||
*.lai
|
||||
*.la
|
||||
*.a
|
||||
*.lib
|
||||
|
||||
# Executables
|
||||
*.exe
|
||||
*.out
|
||||
*.app
|
||||
|
||||
build/*
|
||||
112
CMakeLists.txt
112
CMakeLists.txt
@ -1,112 +0,0 @@
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
|
||||
project(menu VERSION 1.3.0 LANGUAGES C)
|
||||
|
||||
# 设置C标准
|
||||
set(CMAKE_C_STANDARD 99)
|
||||
set(CMAKE_C_STANDARD_REQUIRED ON)
|
||||
|
||||
# 核心层源文件
|
||||
set(CORE_SOURCES
|
||||
src/core/menu_core.c
|
||||
src/core/menu_event.c
|
||||
src/core/menu_hash.c
|
||||
src/core/menu_stack.c
|
||||
src/core/menu_permission.c
|
||||
src/core/menu_persistence.c
|
||||
)
|
||||
|
||||
# 功能扩展层源文件
|
||||
set(FEATURE_SOURCES
|
||||
src/param/menu_param.c
|
||||
src/lang/menu_lang.c
|
||||
)
|
||||
|
||||
# API层源文件
|
||||
set(API_SOURCES
|
||||
api/menu_api.c
|
||||
)
|
||||
|
||||
# 硬件端口层源文件
|
||||
set(PORT_SOURCES
|
||||
port/menu_port.c
|
||||
)
|
||||
|
||||
# 所有源文件
|
||||
set(MENU_SOURCES
|
||||
${CORE_SOURCES}
|
||||
${FEATURE_SOURCES}
|
||||
${API_SOURCES}
|
||||
${PORT_SOURCES}
|
||||
)
|
||||
|
||||
# 核心层头文件
|
||||
set(CORE_HEADERS
|
||||
src/core/menu_config.h
|
||||
src/core/menu_types.h
|
||||
src/core/menu_permission.h
|
||||
src/core/menu_persistence.h
|
||||
)
|
||||
|
||||
# 功能扩展层头文件
|
||||
set(FEATURE_HEADERS
|
||||
src/param/menu_param.h
|
||||
src/lang/menu_lang.h
|
||||
)
|
||||
|
||||
# API层头文件
|
||||
set(API_HEADERS
|
||||
api/menu_api.h
|
||||
)
|
||||
|
||||
# 硬件端口层头文件
|
||||
set(PORT_HEADERS
|
||||
port/menu_port.h
|
||||
)
|
||||
|
||||
# 所有头文件
|
||||
set(MENU_HEADERS
|
||||
${CORE_HEADERS}
|
||||
${FEATURE_HEADERS}
|
||||
${API_HEADERS}
|
||||
${PORT_HEADERS}
|
||||
)
|
||||
|
||||
# 创建静态库
|
||||
add_library(menu STATIC ${MENU_SOURCES})
|
||||
|
||||
# 设置库的公共头文件
|
||||
set_target_properties(menu PROPERTIES
|
||||
PUBLIC_HEADER "${MENU_HEADERS}"
|
||||
)
|
||||
|
||||
# 包含目录
|
||||
target_include_directories(menu PUBLIC
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src/core>
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src/param>
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src/lang>
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/api>
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/port>
|
||||
$<INSTALL_INTERFACE:include>
|
||||
)
|
||||
|
||||
# 示例程序
|
||||
option(BUILD_EXAMPLES "Build examples" OFF)
|
||||
|
||||
if(BUILD_EXAMPLES)
|
||||
# 添加示例目录
|
||||
add_subdirectory(examples)
|
||||
endif()
|
||||
|
||||
# 添加demo目录
|
||||
add_subdirectory(demo)
|
||||
|
||||
# 添加test目录
|
||||
add_subdirectory(test)
|
||||
|
||||
# 安装规则
|
||||
install(TARGETS menu
|
||||
ARCHIVE DESTINATION lib
|
||||
PUBLIC_HEADER DESTINATION include
|
||||
)
|
||||
214
README.md
214
README.md
@ -1,214 +0,0 @@
|
||||
# 轻量级菜单框架(C语言)
|
||||
|
||||
## 介绍
|
||||
|
||||
### 多级菜单
|
||||
|
||||
同级菜单以数组的方式体现,父菜单和子菜单的关联则使用链表实现。
|
||||
|
||||
> 数组元素内容有:
|
||||
>
|
||||
> * 菜单选项描述方式可选字符串或文本ID(多国语言时,可以统一管理翻译文件,而文本ID可以适用于数组管理的翻译文件)
|
||||
> * 菜单选项进入回调函数:当前菜单选项进入时(从父菜单进入)需要执行一次的函数
|
||||
> * 菜单选项退出回调函数:当前菜单选项进入后退出时(退出至父菜单)需要执行一次的函数
|
||||
> * 菜单选项重加载回调函数:当前菜单选项每次加载时(从父菜单进入或子菜单退出)需要执行一次的函数
|
||||
> * 菜单选项周期调度回调函数:当前菜单选项的周期调度函数
|
||||
> * 菜单选项的扩展数据
|
||||
>
|
||||
> 链表内存可以选择采用动态内存分配或者数组实现
|
||||
|
||||
方便对不同菜单界面功能解耦
|
||||
|
||||
> 大部分菜单采用的都是数组中包含了所有不同级别的菜单选项内容实现,无法做到很好的解耦方式;
|
||||
>
|
||||
> 该模块通过动态绑定子菜单和链表的方式可以达到较好的解耦状态
|
||||
|
||||
### 显示效果
|
||||
|
||||
该框架只负责菜单选项控制操作,不负责在图像界面显示效果,需要在对应的回调函数中实现菜单显示效果。
|
||||
|
||||
> 设置对应的效果显示函数,即可为不同的菜单设置不同的菜单显示效果,比如图标式、列表式或右侧弹窗式等。
|
||||
>
|
||||
> 可以在不同显示平台体现,比如LCD、OLED或终端界面等。
|
||||
|
||||
### 多语种
|
||||
|
||||
没有直接的语种设置,而是通过联合体提供选择多种选择。
|
||||
|
||||
> 文本ID: 适用于多语种采用数组定义的翻译内容
|
||||
> 字符串: 适用于单语种或者多语种,采用映射的方式实现的翻译内容
|
||||
|
||||
### 可扩展
|
||||
|
||||
每级菜单选项都可以设置自定义数据,用来实现更多的菜单操作或者显示效果等。
|
||||
|
||||
> 不同级别的菜单可以设置自定义数据(比如菜单选项隐藏/图标数据等)
|
||||
|
||||
### 可配置
|
||||
|
||||
| 配置选项 | 描述 |
|
||||
| --------------------------- | ------------------------------------------------------------- |
|
||||
| `_COT_MENU_USE_MALLOC_` | 定义则采用 malloc/free 的方式实现多级菜单, 否则通过数组的形式 |
|
||||
| `_COT_MENU_USE_SHORTCUT_` | 定义则启用快捷菜单选项进入功能 |
|
||||
| `COT_MENU_MAX_DEPTH` | 多级菜单深度 |
|
||||
| `COT_MENU_MAX_NUM` | 菜单支持的最大选项数目 |
|
||||
|
||||
### 功能多样化
|
||||
|
||||
* [X] 支持快速进入指定菜单界面。
|
||||
|
||||
> - 可以通过相对选项索引或者绝对选项索引路径实现
|
||||
>
|
||||
* [X] 可以实现有限界面内显示少量的菜单选项内容。
|
||||
|
||||
> - 有现成的函数可用,无需担心使用不同尺寸重新实现菜单选项部分可见
|
||||
>
|
||||
|
||||
## 软件设计
|
||||
|
||||
略
|
||||
|
||||
## 使用说明
|
||||
|
||||
### 菜单初始化和使用
|
||||
|
||||
```c
|
||||
|
||||
// 定义菜单信息,函数由主菜单模块定义并提供
|
||||
static cotMainMenuCfg_t sg_tMainMenu = {"主菜单", Hmi_EnterMainHmi, NULL, NULL, NULL};
|
||||
|
||||
int main(void)
|
||||
{
|
||||
cotMenu_Init(&sg_tMainMenu);
|
||||
|
||||
while (1)
|
||||
{
|
||||
...
|
||||
|
||||
if (timeFlag)
|
||||
{
|
||||
timeFlag = 0;
|
||||
cotMenu_Task(); // 周期调度
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### 主菜单定义和绑定
|
||||
|
||||
定义一个主菜单选项内容、主菜单显示效果函数和主菜单进入函数等
|
||||
|
||||
```c
|
||||
|
||||
// 扩展数据为图标文件名字,每个菜单选项描述为文本ID
|
||||
cotMenuList_t sg_MainMenuTable[] =
|
||||
{
|
||||
COT_MENU_ITEM_BIND(TEXT_MUSIC, Hmi_MusicEnter, Hmi_MusicExit, Hmi_MusicLoad, Hmi_MusicTask, (MenuImage_t *)&sgc_MusicImage),
|
||||
|
||||
COT_MENU_ITEM_BIND(TEXT_VIDEO, NULL, Hmi_VideoExit, Hmi_VideoLoad, Hmi_VideoTask, (MenuImage_t *)&sgc_VideoImage),
|
||||
|
||||
COT_MENU_ITEM_BIND(TEXT_CAMERA, Hmi_CameraEnter, Hmi_CameraExit, Hmi_CameraLoad, Hmi_CameraTask, (MenuImage_t *)&sgc_CameraImage),
|
||||
|
||||
COT_MENU_ITEM_BIND(TEXT_SETTING, Hmi_SetEnter, Hmi_SetExit, Hmi_SetLoad, Hmi_SetTask, (MenuImage_t *)&sgc_SettingImage),
|
||||
};
|
||||
|
||||
/* 主菜单显示效果 */
|
||||
static void ShowMainMenu(cotMenuShow_t *ptShowInfo)
|
||||
{
|
||||
char *pszSelectDesc = get_text(ptShowInfo->pszItemsDesc[ptShowInfo->selectItem].textId); // 根据文本ID找到对应的字符串(多语言)
|
||||
oledsize_t idx = (128 - 6 * strlen(pszSelectDesc)) / 2;
|
||||
|
||||
cotOled_DrawGraphic(40, 0, (const char *)ptShowInfo->pItemsListExtendData[ptShowInfo->selectItem], 1);
|
||||
|
||||
cotOled_SetText(0, 50, " ", 0, FONT_12X12);
|
||||
cotOled_SetText(idx, 50, pszSelectDesc, 0, FONT_12X12);
|
||||
}
|
||||
|
||||
void Hmi_EnterMainHmi(const cotMenuItemInfo_t *pItemInfo)
|
||||
{
|
||||
cotMenu_Bind(sg_MainMenuTable, COT_GET_MENU_NUM(sg_MainMenuTable), ShowMainMenu);
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### 子菜单定义和绑定
|
||||
|
||||
如果菜单选项有子菜单,则该菜单选项调用 `cotMenu_Enter`,进入回调函数**不能为NULL**,且该回调函数需调用 `cotMenu_Bind`进行绑定
|
||||
|
||||
```c
|
||||
|
||||
/* 设置的子菜单内容,每个菜单选项描述为字符串 */
|
||||
cotMenuList_t sg_SetMenuTable[] =
|
||||
{
|
||||
COT_MENU_ITEM_BIND("语言", NULL, NULL, NULL, OnLanguageFunction, NULL),
|
||||
COT_MENU_ITEM_BIND("蓝牙", NULL, NULL, NULL, OnBluetoothFunction, NULL),
|
||||
COT_MENU_ITEM_BIND("电池", NULL, NULL, NULL, OnBatteryFunction, NULL),
|
||||
COT_MENU_ITEM_BIND("储存", NULL, NULL, NULL, OnStorageFunction, NULL),
|
||||
COT_MENU_ITEM_BIND("更多", Hmi_MoreSetEnter, Hmi_MoreSetExit, Hmi_MoreSetLoad, Hmi_MoreSetTask, NULL),
|
||||
};
|
||||
|
||||
/* 设置菜单显示效果 */
|
||||
static void ShowSetMenu(cotMenuShow_t *ptShowInfo)
|
||||
{
|
||||
uint8_t showNum = 3;
|
||||
menusize_t tmpselect;
|
||||
|
||||
cotMenu_LimitShowListNum(ptShowInfo, &showNum);
|
||||
|
||||
// 这里直接使用字符串
|
||||
printf("\e[0;30;46m ------------- %s ------------- \e[0m\n", tParentMenuShowInfo.uMenuDesc.pTextString);
|
||||
|
||||
for (int i = 0; i < showNum; i++)
|
||||
{
|
||||
tmpselect = i + ptShowInfo->showBaseItem;
|
||||
|
||||
if (tmpselect == ptShowInfo->selectItem)
|
||||
{
|
||||
printf("\e[0;30;47m %d. %-16s\e[0m |\n", tmpselect + 1, ptShowInfo->pszItemsDesc[tmpselect].pTextString);
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("\e[7;30;47m %d. %-16s\e[0m |\n", tmpselect + 1, ptShowInfo->pszItemsDesc[tmpselect].pTextString);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Hmi_SetEnter(const cotMenuItemInfo_t *pItemInfo)
|
||||
{
|
||||
// 进入设置选项后绑定子菜单,同时为当前绑定的菜单设置显示效果函数
|
||||
cotMenu_Bind(sg_SetMenuTable, COT_GET_MENU_NUM(sg_SetMenuTable), ShowSetMenu);
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### 菜单控制
|
||||
|
||||
通过调用相关函数实现菜单选项选择、进入、退出等
|
||||
|
||||
```c
|
||||
|
||||
// 需要先进入主菜单
|
||||
cotMenu_MainEnter();
|
||||
|
||||
// 选择上一个,支持循环选择(即第一个可跳转到最后一个)
|
||||
cotMenu_SelectPrevious(true);
|
||||
|
||||
// 选择下一个,不支持循环选择(即最后一个不可跳转到第一个)
|
||||
cotMenu_SelectNext(false);
|
||||
|
||||
// 进入,会执行菜单选项的 pfnEnterCallFun 回调函数
|
||||
cotMenu_Enter();
|
||||
|
||||
// 退出,会执行父菜单该选项的 pfnExitCallFun 回调函数,并在退出后父菜单选项列表复位从头选择
|
||||
cotMenu_Exit(true);
|
||||
|
||||
```
|
||||
|
||||
定义了多个菜单选项表后,用来区分上下级,在某个菜单选项进入时绑定
|
||||
|
||||
1. 使用前初始化函数 cotMenu_Init, 设置主菜单内容
|
||||
2. 周期调用函数 cotMenu_Task, 用来处理菜单显示和执行相关回调函数
|
||||
3. 使用其他函数对菜单界面控制
|
||||
|
||||
|
||||
260
api/menu_api.c
260
api/menu_api.c
@ -1,260 +0,0 @@
|
||||
/**
|
||||
**********************************************************************************************************************
|
||||
* @file menu_api.c
|
||||
* @brief 菜单组件对外API接口实现
|
||||
* @author menu_component
|
||||
* @date 2025-12-19
|
||||
**********************************************************************************************************************
|
||||
*/
|
||||
|
||||
/* Includes ----------------------------------------------------------------------------------------------------------*/
|
||||
#include "menu_api.h"
|
||||
#include "../src/core/menu_core.h"
|
||||
#include "../src/core/menu_event.h"
|
||||
#include "../src/core/menu_hash.h"
|
||||
|
||||
/* 函数实现 ---------------------------------------------------------------------------------------------------------*/
|
||||
|
||||
/* 菜单初始化与反初始化 ---------------------------------------------------------------------------------------------*/
|
||||
|
||||
MenuErrCode menu_init(void)
|
||||
{
|
||||
return menu_core_init();
|
||||
}
|
||||
|
||||
MenuErrCode menu_deinit(void)
|
||||
{
|
||||
return menu_core_deinit();
|
||||
}
|
||||
|
||||
/* 菜单节点管理 -----------------------------------------------------------------------------------------------------*/
|
||||
|
||||
MenuNodeId menu_register_node(MenuNodeId node_id, MenuNodeId parent_id, const char* name, MenuCallback enter_cb, MenuCallback exit_cb)
|
||||
{
|
||||
// 参数验证
|
||||
if (name == NULL) {
|
||||
return 0; // 保持原有API兼容,0表示注册失败
|
||||
}
|
||||
|
||||
// 调用核心层的节点注册功能
|
||||
return menu_core_register_node(node_id, parent_id, name, enter_cb, exit_cb);
|
||||
}
|
||||
|
||||
MenuErrCode menu_register_nodes(const struct MenuNode* nodes, uint16_t count)
|
||||
{
|
||||
// 暂未实现批量注册功能
|
||||
(void)nodes;
|
||||
(void)count;
|
||||
return MENU_ERR_OPERATION_FAILED;
|
||||
}
|
||||
|
||||
MenuErrCode menu_unregister_node(MenuNodeId node_id)
|
||||
{
|
||||
// 调用核心层的节点注销功能
|
||||
return menu_core_unregister_node(node_id);
|
||||
}
|
||||
|
||||
MenuErrCode menu_update_node(MenuNodeId node_id, const char* name, MenuCallback enter_cb, MenuCallback exit_cb)
|
||||
{
|
||||
MenuCoreCtx* core_ctx = menu_get_core_ctx();
|
||||
MenuNode* node = menu_hash_find(core_ctx, node_id);
|
||||
|
||||
if (node == NULL || !node->flags.is_registered) {
|
||||
return MENU_ERR_NODE_NOT_FOUND;
|
||||
}
|
||||
|
||||
// 更新节点信息
|
||||
if (name != NULL) {
|
||||
node->name = name;
|
||||
}
|
||||
|
||||
if (enter_cb != NULL) {
|
||||
node->enter_cb = enter_cb;
|
||||
}
|
||||
|
||||
if (exit_cb != NULL) {
|
||||
node->exit_cb = exit_cb;
|
||||
}
|
||||
|
||||
return MENU_ERR_OK;
|
||||
}
|
||||
|
||||
const struct MenuNode* menu_find_node(MenuNodeId node_id)
|
||||
{
|
||||
MenuCoreCtx* core_ctx = menu_get_core_ctx();
|
||||
return menu_hash_find(core_ctx, node_id);
|
||||
}
|
||||
|
||||
/* 菜单事件处理 -----------------------------------------------------------------------------------------------------*/
|
||||
|
||||
MenuErrCode menu_post_event(MenuEventType event_type, uint8_t priority)
|
||||
{
|
||||
MenuCoreCtx* core_ctx = menu_get_core_ctx();
|
||||
MenuEvent event;
|
||||
|
||||
event.type = event_type;
|
||||
event.priority = priority;
|
||||
event.target_node_id = core_ctx->current_node_id;
|
||||
event.data = NULL;
|
||||
event.timestamp = core_ctx->last_refresh_tick;
|
||||
|
||||
return menu_event_queue_push(&core_ctx->event_queue, &event);
|
||||
}
|
||||
|
||||
MenuErrCode menu_post_event_with_data(MenuEventType event_type, uint8_t priority, void* data)
|
||||
{
|
||||
MenuCoreCtx* core_ctx = menu_get_core_ctx();
|
||||
MenuEvent event;
|
||||
|
||||
event.type = event_type;
|
||||
event.priority = priority;
|
||||
event.target_node_id = core_ctx->current_node_id;
|
||||
event.data = data;
|
||||
event.timestamp = core_ctx->last_refresh_tick;
|
||||
|
||||
return menu_event_queue_push(&core_ctx->event_queue, &event);
|
||||
}
|
||||
|
||||
/* 菜单导航操作 -----------------------------------------------------------------------------------------------------*/
|
||||
|
||||
MenuErrCode menu_main_loop(uint32_t tick)
|
||||
{
|
||||
// 调用核心层的主循环功能
|
||||
return menu_core_main_loop(tick);
|
||||
}
|
||||
|
||||
MenuErrCode menu_enter(void)
|
||||
{
|
||||
return menu_post_event(MENU_EVENT_KEY_ENTER, 1);
|
||||
}
|
||||
|
||||
MenuErrCode menu_exit(void)
|
||||
{
|
||||
return menu_post_event(MENU_EVENT_KEY_ESC, 1);
|
||||
}
|
||||
|
||||
MenuErrCode menu_up(void)
|
||||
{
|
||||
return menu_post_event(MENU_EVENT_KEY_UP, 1);
|
||||
}
|
||||
|
||||
MenuErrCode menu_down(void)
|
||||
{
|
||||
return menu_post_event(MENU_EVENT_KEY_DOWN, 1);
|
||||
}
|
||||
|
||||
MenuErrCode menu_select(MenuNodeId node_id)
|
||||
{
|
||||
// 暂未实现直接选择指定节点功能
|
||||
(void)node_id;
|
||||
return MENU_ERR_OPERATION_FAILED;
|
||||
}
|
||||
|
||||
MenuErrCode menu_reset(void)
|
||||
{
|
||||
// 暂未实现重置功能
|
||||
return MENU_ERR_OPERATION_FAILED;
|
||||
}
|
||||
|
||||
/* 菜单状态查询 -----------------------------------------------------------------------------------------------------*/
|
||||
|
||||
MenuState menu_get_state(void)
|
||||
{
|
||||
return menu_get_current_state();
|
||||
}
|
||||
|
||||
MenuNodeId menu_get_current_node(void)
|
||||
{
|
||||
return menu_get_current_node_id();
|
||||
}
|
||||
|
||||
uint8_t menu_get_nav_depth(void)
|
||||
{
|
||||
MenuCoreCtx* core_ctx = menu_get_core_ctx();
|
||||
return core_ctx->nav_path.depth;
|
||||
}
|
||||
|
||||
uint8_t menu_get_nav_path(MenuNodeId* path, uint8_t max_depth)
|
||||
{
|
||||
MenuCoreCtx* core_ctx = menu_get_core_ctx();
|
||||
uint8_t actual_depth = core_ctx->nav_path.depth;
|
||||
|
||||
if (actual_depth > max_depth) {
|
||||
actual_depth = max_depth;
|
||||
}
|
||||
|
||||
for (uint8_t i = 0; i < actual_depth; i++) {
|
||||
path[i] = core_ctx->nav_path.path[i];
|
||||
}
|
||||
|
||||
return actual_depth;
|
||||
}
|
||||
|
||||
/* 状态机扩展接口 ---------------------------------------------------------------------------------------------------*/
|
||||
|
||||
#if MENU_CONFIG_ENABLE_STATE_MACHINE_EXT
|
||||
MenuErrCode menu_state_register_transition(const MenuStateTransition* transition)
|
||||
{
|
||||
MenuCoreCtx* core_ctx = menu_get_core_ctx();
|
||||
|
||||
if (core_ctx->custom_transition_count >= MENU_CONFIG_MAX_STATE_TRANSITIONS) {
|
||||
return MENU_ERR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
core_ctx->custom_transitions[core_ctx->custom_transition_count] = *transition;
|
||||
core_ctx->custom_transition_count++;
|
||||
|
||||
return MENU_ERR_OK;
|
||||
}
|
||||
|
||||
MenuErrCode menu_state_unregister_transition(MenuState current_state, MenuEventType event)
|
||||
{
|
||||
MenuCoreCtx* core_ctx = menu_get_core_ctx();
|
||||
bool found = false;
|
||||
|
||||
// 查找并删除匹配的状态转换规则
|
||||
for (uint16_t i = 0; i < core_ctx->custom_transition_count; i++) {
|
||||
if (core_ctx->custom_transitions[i].current_state == current_state &&
|
||||
core_ctx->custom_transitions[i].event == event) {
|
||||
// 找到匹配的规则,将后续规则前移
|
||||
for (uint16_t j = i; j < core_ctx->custom_transition_count - 1; j++) {
|
||||
core_ctx->custom_transitions[j] = core_ctx->custom_transitions[j + 1];
|
||||
}
|
||||
core_ctx->custom_transition_count--;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return found ? MENU_ERR_OK : MENU_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
MenuErrCode menu_state_switch(MenuState state)
|
||||
{
|
||||
MenuCoreCtx* core_ctx = menu_get_core_ctx();
|
||||
core_ctx->current_state = state;
|
||||
return MENU_ERR_OK;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* 内存监控接口 -----------------------------------------------------------------------------------------------------*/
|
||||
|
||||
#if MENU_CONFIG_ENABLE_MEM_MONITOR
|
||||
uint16_t menu_mem_get_used_nodes(void)
|
||||
{
|
||||
MenuCoreCtx* core_ctx = menu_get_core_ctx();
|
||||
return core_ctx->node_count;
|
||||
}
|
||||
|
||||
uint16_t menu_mem_get_free_nodes(void)
|
||||
{
|
||||
MenuCoreCtx* core_ctx = menu_get_core_ctx();
|
||||
return core_ctx->free_node_count;
|
||||
}
|
||||
|
||||
uint16_t menu_mem_get_max_nodes(void)
|
||||
{
|
||||
return MENU_CONFIG_MAX_NODES;
|
||||
}
|
||||
#endif
|
||||
|
||||
332
api/menu_api.h
332
api/menu_api.h
@ -1,332 +0,0 @@
|
||||
/**
|
||||
**********************************************************************************************************************
|
||||
* @file menu_api.h
|
||||
* @brief 菜单组件对外API接口定义
|
||||
* @author menu_component
|
||||
* @date 2025-12-19
|
||||
**********************************************************************************************************************
|
||||
*/
|
||||
|
||||
#ifndef MENU_API_H
|
||||
#define MENU_API_H
|
||||
|
||||
/* Includes ----------------------------------------------------------------------------------------------------------*/
|
||||
#include "../src/core/menu_config.h"
|
||||
#include "../src/core/menu_types.h"
|
||||
|
||||
/* 宏定义 -----------------------------------------------------------------------------------------------------------*/
|
||||
#define MENU_API_VERSION "1.0.1"
|
||||
|
||||
/* 函数声明 ---------------------------------------------------------------------------------------------------------*/
|
||||
|
||||
/* 菜单初始化与反初始化 ---------------------------------------------------------------------------------------------*/
|
||||
|
||||
/**
|
||||
* @brief 初始化菜单组件
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_init(void);
|
||||
|
||||
/**
|
||||
* @brief 反初始化菜单组件
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_deinit(void);
|
||||
|
||||
/* 菜单节点管理 -----------------------------------------------------------------------------------------------------*/
|
||||
|
||||
/**
|
||||
* @brief 注册菜单节点
|
||||
* @param node_id 节点ID(0表示自动分配)
|
||||
* @param parent_id 父节点ID
|
||||
* @param name 节点名称
|
||||
* @param enter_cb 进入回调
|
||||
* @param exit_cb 退出回调
|
||||
* @return 实际分配的节点ID
|
||||
*/
|
||||
MenuNodeId menu_register_node(MenuNodeId node_id, MenuNodeId parent_id, const char* name, MenuCallback enter_cb, MenuCallback exit_cb);
|
||||
|
||||
/**
|
||||
* @brief 批量注册菜单节点
|
||||
* @param nodes 节点数组
|
||||
* @param count 节点数量
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_register_nodes(const struct MenuNode* nodes, uint16_t count);
|
||||
|
||||
/**
|
||||
* @brief 注销菜单节点
|
||||
* @param node_id 节点ID
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_unregister_node(MenuNodeId node_id);
|
||||
|
||||
/**
|
||||
* @brief 更新菜单节点
|
||||
* @param node_id 节点ID
|
||||
* @param name 节点名称(NULL表示不更新)
|
||||
* @param enter_cb 进入回调(NULL表示不更新)
|
||||
* @param exit_cb 退出回调(NULL表示不更新)
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_update_node(MenuNodeId node_id, const char* name, MenuCallback enter_cb, MenuCallback exit_cb);
|
||||
|
||||
/**
|
||||
* @brief 查找菜单节点
|
||||
* @param node_id 节点ID
|
||||
* @return 节点指针,未找到返回NULL
|
||||
*/
|
||||
const struct MenuNode* menu_find_node(MenuNodeId node_id);
|
||||
|
||||
/* 菜单事件处理 -----------------------------------------------------------------------------------------------------*/
|
||||
|
||||
/**
|
||||
* @brief 发送菜单事件
|
||||
* @param event_type 事件类型
|
||||
* @param priority 事件优先级(0-3,0最高)
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_post_event(MenuEventType event_type, uint8_t priority);
|
||||
|
||||
/**
|
||||
* @brief 发送带数据的菜单事件
|
||||
* @param event_type 事件类型
|
||||
* @param priority 事件优先级(0-3,0最高)
|
||||
* @param data 事件数据
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_post_event_with_data(MenuEventType event_type, uint8_t priority, void* data);
|
||||
|
||||
/* 菜单导航操作 -----------------------------------------------------------------------------------------------------*/
|
||||
|
||||
/**
|
||||
* @brief 菜单主循环,处理事件和刷新显示
|
||||
* @param tick 当前系统时间(ms)
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_main_loop(uint32_t tick);
|
||||
|
||||
/**
|
||||
* @brief 进入菜单
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_enter(void);
|
||||
|
||||
/**
|
||||
* @brief 退出菜单
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_exit(void);
|
||||
|
||||
/**
|
||||
* @brief 上移菜单项
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_up(void);
|
||||
|
||||
/**
|
||||
* @brief 下移菜单项
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_down(void);
|
||||
|
||||
/**
|
||||
* @brief 选择菜单项
|
||||
* @param node_id 节点ID
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_select(MenuNodeId node_id);
|
||||
|
||||
/**
|
||||
* @brief 重置菜单,回到主菜单
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_reset(void);
|
||||
|
||||
/* 菜单状态查询 -----------------------------------------------------------------------------------------------------*/
|
||||
|
||||
/**
|
||||
* @brief 获取当前菜单状态
|
||||
* @return 当前菜单状态
|
||||
*/
|
||||
MenuState menu_get_state(void);
|
||||
|
||||
/**
|
||||
* @brief 获取当前选中的节点ID
|
||||
* @return 当前节点ID
|
||||
*/
|
||||
MenuNodeId menu_get_current_node(void);
|
||||
|
||||
/**
|
||||
* @brief 获取导航深度
|
||||
* @return 导航深度
|
||||
*/
|
||||
uint8_t menu_get_nav_depth(void);
|
||||
|
||||
/**
|
||||
* @brief 获取导航路径
|
||||
* @param path 用于存储导航路径的数组
|
||||
* @param max_depth 数组最大深度
|
||||
* @return 实际获取的深度
|
||||
*/
|
||||
uint8_t menu_get_nav_path(MenuNodeId* path, uint8_t max_depth);
|
||||
|
||||
/* 状态机扩展接口 ---------------------------------------------------------------------------------------------------*/
|
||||
|
||||
#if MENU_CONFIG_ENABLE_STATE_MACHINE_EXT
|
||||
/**
|
||||
* @brief 注册自定义状态转换规则
|
||||
* @param transition 状态转换规则
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_state_register_transition(const MenuStateTransition* transition);
|
||||
|
||||
/**
|
||||
* @brief 注销自定义状态转换规则
|
||||
* @param current_state 当前状态
|
||||
* @param event 触发事件
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_state_unregister_transition(MenuState current_state, MenuEventType event);
|
||||
|
||||
/**
|
||||
* @brief 切换到指定状态
|
||||
* @param state 目标状态
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_state_switch(MenuState state);
|
||||
#endif
|
||||
|
||||
/* 内存监控接口 -----------------------------------------------------------------------------------------------------*/
|
||||
|
||||
#if MENU_CONFIG_ENABLE_MEM_MONITOR
|
||||
/**
|
||||
* @brief 获取已使用节点数量
|
||||
* @return 已使用节点数量
|
||||
*/
|
||||
uint16_t menu_mem_get_used_nodes(void);
|
||||
|
||||
/**
|
||||
* @brief 获取空闲节点数量
|
||||
* @return 空闲节点数量
|
||||
*/
|
||||
uint16_t menu_mem_get_free_nodes(void);
|
||||
|
||||
/**
|
||||
* @brief 获取最大节点数量
|
||||
* @return 最大节点数量
|
||||
*/
|
||||
uint16_t menu_mem_get_max_nodes(void);
|
||||
#endif
|
||||
|
||||
/* 权限管理接口 -----------------------------------------------------------------------------------------------------*/
|
||||
|
||||
#if MENU_CONFIG_ENABLE_PERMISSION
|
||||
/**
|
||||
* @brief 注册角色
|
||||
* @param role 角色结构体指针
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_permission_register_role(const MenuRole* role);
|
||||
|
||||
/**
|
||||
* @brief 注销角色
|
||||
* @param role_id 角色ID
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_permission_unregister_role(MenuRoleId role_id);
|
||||
|
||||
/**
|
||||
* @brief 设置当前角色
|
||||
* @param role_id 角色ID
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_permission_set_current_role(MenuRoleId role_id);
|
||||
|
||||
/**
|
||||
* @brief 获取当前角色
|
||||
* @return 当前角色ID
|
||||
*/
|
||||
MenuRoleId menu_permission_get_current_role(void);
|
||||
|
||||
/**
|
||||
* @brief 更新节点权限级别
|
||||
* @param node_id 节点ID
|
||||
* @param permission_level 权限级别
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_permission_update_node_level(MenuNodeId node_id, MenuPermissionLevel permission_level);
|
||||
|
||||
/**
|
||||
* @brief 获取节点权限级别
|
||||
* @param node_id 节点ID
|
||||
* @return 节点权限级别
|
||||
*/
|
||||
MenuPermissionLevel menu_permission_get_node_level(MenuNodeId node_id);
|
||||
|
||||
/**
|
||||
* @brief 检查节点是否可访问
|
||||
* @param node_id 节点ID
|
||||
* @return true表示可访问,false表示不可访问
|
||||
*/
|
||||
bool menu_permission_check_node_access(MenuNodeId node_id);
|
||||
|
||||
/**
|
||||
* @brief 获取角色信息
|
||||
* @param role_id 角色ID
|
||||
* @return 角色结构体指针,NULL表示角色不存在
|
||||
*/
|
||||
const MenuRole* menu_permission_get_role(MenuRoleId role_id);
|
||||
#endif
|
||||
|
||||
/* 状态持久化接口 ---------------------------------------------------------------------------------------------------*/
|
||||
|
||||
#if MENU_CONFIG_ENABLE_PERSISTENCE
|
||||
/**
|
||||
* @brief 注册持久化回调函数
|
||||
* @param save_cb 保存回调函数
|
||||
* @param restore_cb 恢复回调函数
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_persistence_register_callback(MenuPersistenceCallback save_cb, MenuRestoreCallback restore_cb);
|
||||
|
||||
/**
|
||||
* @brief 启用或禁用自动保存
|
||||
* @param enable true表示启用,false表示禁用
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_persistence_set_auto_save(bool enable);
|
||||
|
||||
/**
|
||||
* @brief 获取自动保存状态
|
||||
* @return true表示启用,false表示禁用
|
||||
*/
|
||||
bool menu_persistence_get_auto_save(void);
|
||||
|
||||
/**
|
||||
* @brief 设置自动保存间隔
|
||||
* @param interval 自动保存间隔(ms)
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_persistence_set_auto_save_interval(uint32_t interval);
|
||||
|
||||
/**
|
||||
* @brief 获取自动保存间隔
|
||||
* @return 自动保存间隔(ms)
|
||||
*/
|
||||
uint32_t menu_persistence_get_auto_save_interval(void);
|
||||
|
||||
/**
|
||||
* @brief 手动保存菜单状态
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_persistence_save(void);
|
||||
|
||||
/**
|
||||
* @brief 恢复菜单状态
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_persistence_restore(void);
|
||||
#endif
|
||||
|
||||
#endif /* MENU_API_H */
|
||||
@ -1,30 +0,0 @@
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
|
||||
project(menu_demo C)
|
||||
|
||||
# 设置C标准
|
||||
set(CMAKE_C_STANDARD 99)
|
||||
set(CMAKE_C_STANDARD_REQUIRED ON)
|
||||
|
||||
# 添加当前目录和上级目录作为包含目录
|
||||
include_directories(
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
${CMAKE_SOURCE_DIR}
|
||||
)
|
||||
|
||||
# 收集示例所需的源文件
|
||||
set(DEMO_SOURCES
|
||||
demo.c
|
||||
language.c
|
||||
)
|
||||
|
||||
# 创建demo可执行程序
|
||||
add_executable(menu_demo ${DEMO_SOURCES})
|
||||
|
||||
# 链接menu库
|
||||
target_link_libraries(menu_demo PRIVATE menu)
|
||||
|
||||
# 安装规则
|
||||
install(TARGETS menu_demo
|
||||
RUNTIME DESTINATION bin
|
||||
)
|
||||
@ -1,92 +0,0 @@
|
||||
# This is the CMakeCache file.
|
||||
# For build in directory: e:/Jfen_work/local_git_code/menu/demo/build
|
||||
# It was generated by CMake: C:/Users/ZHIZHANKEJI/AppData/Local/Programs/Python/Python311/Lib/site-packages/cmake/data/bin/cmake.exe
|
||||
# You can edit this file to change values found and used by cmake.
|
||||
# If you do not want to change any of the values, simply exit the editor.
|
||||
# If you do want to change a value, simply edit, save, and exit the editor.
|
||||
# The syntax for the file is as follows:
|
||||
# KEY:TYPE=VALUE
|
||||
# KEY is the name of a variable in the cache.
|
||||
# TYPE is a hint to GUIs for the type of VALUE, DO NOT EDIT TYPE!.
|
||||
# VALUE is the current value for the KEY.
|
||||
|
||||
########################
|
||||
# EXTERNAL cache entries
|
||||
########################
|
||||
|
||||
//Value Computed by CMake.
|
||||
CMAKE_FIND_PACKAGE_REDIRECTS_DIR:STATIC=E:/Jfen_work/local_git_code/menu/demo/build/CMakeFiles/pkgRedirects
|
||||
|
||||
//Program used to build from makefiles.
|
||||
CMAKE_MAKE_PROGRAM:STRING=nmake
|
||||
|
||||
//Value Computed by CMake
|
||||
CMAKE_PROJECT_DESCRIPTION:STATIC=
|
||||
|
||||
//Value Computed by CMake
|
||||
CMAKE_PROJECT_HOMEPAGE_URL:STATIC=
|
||||
|
||||
//Value Computed by CMake
|
||||
CMAKE_PROJECT_NAME:STATIC=menu_demo
|
||||
|
||||
//Value Computed by CMake
|
||||
demo_BINARY_DIR:STATIC=E:/Jfen_work/local_git_code/menu/demo/build
|
||||
|
||||
//Value Computed by CMake
|
||||
demo_IS_TOP_LEVEL:STATIC=ON
|
||||
|
||||
//Value Computed by CMake
|
||||
demo_SOURCE_DIR:STATIC=E:/Jfen_work/local_git_code/menu/demo
|
||||
|
||||
//Value Computed by CMake
|
||||
menu_demo_BINARY_DIR:STATIC=E:/Jfen_work/local_git_code/menu/demo/build
|
||||
|
||||
//Value Computed by CMake
|
||||
menu_demo_IS_TOP_LEVEL:STATIC=ON
|
||||
|
||||
//Value Computed by CMake
|
||||
menu_demo_SOURCE_DIR:STATIC=E:/Jfen_work/local_git_code/menu/demo
|
||||
|
||||
|
||||
########################
|
||||
# INTERNAL cache entries
|
||||
########################
|
||||
|
||||
//This is the directory where this CMakeCache.txt was created
|
||||
CMAKE_CACHEFILE_DIR:INTERNAL=e:/Jfen_work/local_git_code/menu/demo/build
|
||||
//Major version of cmake used to create the current loaded cache
|
||||
CMAKE_CACHE_MAJOR_VERSION:INTERNAL=3
|
||||
//Minor version of cmake used to create the current loaded cache
|
||||
CMAKE_CACHE_MINOR_VERSION:INTERNAL=31
|
||||
//Patch version of cmake used to create the current loaded cache
|
||||
CMAKE_CACHE_PATCH_VERSION:INTERNAL=2
|
||||
//Path to CMake executable.
|
||||
CMAKE_COMMAND:INTERNAL=C:/Users/ZHIZHANKEJI/AppData/Local/Programs/Python/Python311/Lib/site-packages/cmake/data/bin/cmake.exe
|
||||
//Path to cpack program executable.
|
||||
CMAKE_CPACK_COMMAND:INTERNAL=C:/Users/ZHIZHANKEJI/AppData/Local/Programs/Python/Python311/Lib/site-packages/cmake/data/bin/cpack.exe
|
||||
//Path to ctest program executable.
|
||||
CMAKE_CTEST_COMMAND:INTERNAL=C:/Users/ZHIZHANKEJI/AppData/Local/Programs/Python/Python311/Lib/site-packages/cmake/data/bin/ctest.exe
|
||||
//Path to cache edit program executable.
|
||||
CMAKE_EDIT_COMMAND:INTERNAL=C:/Users/ZHIZHANKEJI/AppData/Local/Programs/Python/Python311/Lib/site-packages/cmake/data/bin/cmake-gui.exe
|
||||
//Name of external makefile project generator.
|
||||
CMAKE_EXTRA_GENERATOR:INTERNAL=
|
||||
//Name of generator.
|
||||
CMAKE_GENERATOR:INTERNAL=NMake Makefiles
|
||||
//Generator instance identifier.
|
||||
CMAKE_GENERATOR_INSTANCE:INTERNAL=
|
||||
//Name of generator platform.
|
||||
CMAKE_GENERATOR_PLATFORM:INTERNAL=
|
||||
//Name of generator toolset.
|
||||
CMAKE_GENERATOR_TOOLSET:INTERNAL=
|
||||
//Source directory with the top level CMakeLists.txt file for this
|
||||
// project
|
||||
CMAKE_HOME_DIRECTORY:INTERNAL=E:/Jfen_work/local_git_code/menu/demo
|
||||
//ADVANCED property for variable: CMAKE_MAKE_PROGRAM
|
||||
CMAKE_MAKE_PROGRAM-ADVANCED:INTERNAL=1
|
||||
//number of local generators
|
||||
CMAKE_NUMBER_OF_MAKEFILES:INTERNAL=1
|
||||
//Platform information initialized
|
||||
CMAKE_PLATFORM_INFO_INITIALIZED:INTERNAL=1
|
||||
//Path to CMake installation.
|
||||
CMAKE_ROOT:INTERNAL=C:/Users/ZHIZHANKEJI/AppData/Local/Programs/Python/Python311/Lib/site-packages/cmake/data/share/cmake-3.31
|
||||
|
||||
@ -1,15 +0,0 @@
|
||||
set(CMAKE_HOST_SYSTEM "Windows-10.0.26200")
|
||||
set(CMAKE_HOST_SYSTEM_NAME "Windows")
|
||||
set(CMAKE_HOST_SYSTEM_VERSION "10.0.26200")
|
||||
set(CMAKE_HOST_SYSTEM_PROCESSOR "AMD64")
|
||||
|
||||
|
||||
|
||||
set(CMAKE_SYSTEM "Windows-10.0.26200")
|
||||
set(CMAKE_SYSTEM_NAME "Windows")
|
||||
set(CMAKE_SYSTEM_VERSION "10.0.26200")
|
||||
set(CMAKE_SYSTEM_PROCESSOR "AMD64")
|
||||
|
||||
set(CMAKE_CROSSCOMPILING "FALSE")
|
||||
|
||||
set(CMAKE_SYSTEM_LOADED 1)
|
||||
@ -1,11 +0,0 @@
|
||||
|
||||
---
|
||||
events:
|
||||
-
|
||||
kind: "message-v1"
|
||||
backtrace:
|
||||
- "C:/Users/ZHIZHANKEJI/AppData/Local/Programs/Python/Python311/Lib/site-packages/cmake/data/share/cmake-3.31/Modules/CMakeDetermineSystem.cmake:205 (message)"
|
||||
- "CMakeLists.txt:3 (project)"
|
||||
message: |
|
||||
The system is: Windows - 10.0.26200 - AMD64
|
||||
...
|
||||
@ -1 +0,0 @@
|
||||
# This file is generated by cmake for dependency checking of the CMakeCache.txt file
|
||||
681
demo/demo.c
681
demo/demo.c
@ -1,681 +0,0 @@
|
||||
/**
|
||||
**********************************************************************************************************************
|
||||
* @file demo.c
|
||||
* @brief 菜单组件示例程序 - 多语言版本
|
||||
* @author menu_component
|
||||
* @date 2025-12-19
|
||||
**********************************************************************************************************************
|
||||
*/
|
||||
|
||||
#include "../api/menu_api.h"
|
||||
#include "../port/menu_port.h"
|
||||
#include "language.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
#include <conio.h>
|
||||
#define NON_BLOCKING_GETCHAR() _getch()
|
||||
#define IS_KEY_AVAILABLE() _kbhit()
|
||||
#else
|
||||
#include <sys/time.h>
|
||||
#include <sys/select.h>
|
||||
#include <unistd.h>
|
||||
#include <termios.h>
|
||||
|
||||
// 保存原始终端设置
|
||||
static struct termios sg_original_termios;
|
||||
|
||||
/**
|
||||
* @brief 设置终端为无缓冲模式
|
||||
*/
|
||||
static void set_nonblocking_terminal(void)
|
||||
{
|
||||
struct termios new_termios;
|
||||
tcgetattr(STDIN_FILENO, &sg_original_termios);
|
||||
new_termios = sg_original_termios;
|
||||
new_termios.c_lflag &= ~(ICANON | ECHO); // 关闭行缓冲和回显
|
||||
new_termios.c_cc[VMIN] = 1; // 至少读取1个字符
|
||||
new_termios.c_cc[VTIME] = 0; // 无超时
|
||||
tcsetattr(STDIN_FILENO, TCSANOW, &new_termios);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 恢复终端为原始模式
|
||||
*/
|
||||
static void restore_terminal(void)
|
||||
{
|
||||
tcsetattr(STDIN_FILENO, TCSANOW, &sg_original_termios);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 检查是否有按键输入
|
||||
*/
|
||||
static bool IS_KEY_AVAILABLE(void)
|
||||
{
|
||||
struct timeval tv;
|
||||
fd_set fds;
|
||||
tv.tv_sec = 0;
|
||||
tv.tv_usec = 0;
|
||||
FD_ZERO(&fds);
|
||||
FD_SET(STDIN_FILENO, &fds);
|
||||
return select(STDIN_FILENO + 1, &fds, NULL, NULL, &tv) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 无缓冲获取按键
|
||||
*/
|
||||
static char NON_BLOCKING_GETCHAR(void)
|
||||
{
|
||||
return getchar();
|
||||
}
|
||||
#endif
|
||||
|
||||
/* 使用新的菜单节点ID枚举 */
|
||||
#define MENU_ID_MAIN MENU_NODE_ID_MAIN
|
||||
#define MENU_ID_ITEM1 MENU_NODE_ID_MEASUREMENT
|
||||
#define MENU_ID_ITEM2 MENU_NODE_ID_PROTECTION
|
||||
#define MENU_ID_ITEM3 MENU_NODE_ID_CONTROL
|
||||
#define MENU_ID_SUB1 MENU_NODE_ID_SETTING
|
||||
#define MENU_ID_SUB2 MENU_NODE_ID_RECORD
|
||||
#define MENU_ID_LANGUAGE MENU_NODE_ID_HELP
|
||||
#define MENU_ID_GRANDCHILD1 MENU_NODE_ID_ABOUT
|
||||
#define MENU_ID_GRANDCHILD2 (MENU_NODE_ID_ABOUT + 1)
|
||||
|
||||
/* 测量菜单子项 */
|
||||
#define MENU_ID_MEASUREMENT_VOLTAGE MENU_NODE_ID_MEASUREMENT_VOLTAGE
|
||||
#define MENU_ID_MEASUREMENT_CURRENT MENU_NODE_ID_MEASUREMENT_CURRENT
|
||||
#define MENU_ID_MEASUREMENT_POWER MENU_NODE_ID_MEASUREMENT_POWER
|
||||
#define MENU_ID_MEASUREMENT_ENERGY MENU_NODE_ID_MEASUREMENT_ENERGY
|
||||
#define MENU_ID_MEASUREMENT_FREQUENCY MENU_NODE_ID_MEASUREMENT_FREQUENCY
|
||||
#define MENU_ID_MEASUREMENT_TEMPERATURE MENU_NODE_ID_MEASUREMENT_TEMPERATURE
|
||||
|
||||
/* 保护菜单子项 */
|
||||
#define MENU_ID_PROTECTION_OVER_VOLTAGE MENU_NODE_ID_PROTECTION_OVER_VOLTAGE
|
||||
#define MENU_ID_PROTECTION_UNDER_VOLTAGE MENU_NODE_ID_PROTECTION_UNDER_VOLTAGE
|
||||
#define MENU_ID_PROTECTION_OVER_CURRENT MENU_NODE_ID_PROTECTION_OVER_CURRENT
|
||||
#define MENU_ID_PROTECTION_SETTING MENU_NODE_ID_PROTECTION_SETTING
|
||||
|
||||
/* 控制菜单子项 */
|
||||
#define MENU_ID_CONTROL_ON_OFF MENU_NODE_ID_CONTROL_ON_OFF
|
||||
#define MENU_ID_CONTROL_START_STOP MENU_NODE_ID_CONTROL_START_STOP
|
||||
#define MENU_ID_CONTROL_AUTO_MANUAL MENU_NODE_ID_CONTROL_AUTO_MANUAL
|
||||
|
||||
/* 设置菜单子项 */
|
||||
#define MENU_ID_SETTING_LANGUAGE MENU_NODE_ID_SETTING_LANGUAGE
|
||||
|
||||
/* 持久化函数声明 */
|
||||
static MenuErrCode demo_persistence_save(const uint8_t* data, uint16_t size);
|
||||
static MenuErrCode demo_persistence_restore(uint8_t* data, uint16_t* size);
|
||||
|
||||
/* 全局变量 */
|
||||
static uint32_t sg_tick = 0;
|
||||
static bool sg_is_menu_active = false;
|
||||
static SystemLang_e sg_current_language = SYSTEM_LANG_CHINESE;
|
||||
|
||||
/* 菜单回调函数 */
|
||||
|
||||
/**
|
||||
* @brief 主菜单进入回调
|
||||
*/
|
||||
static MenuErrCode menu_enter_main(MenuNodeId node_id, struct MenuCoreCtx* core_ctx)
|
||||
{
|
||||
(void)node_id;
|
||||
(void)core_ctx;
|
||||
sg_is_menu_active = true;
|
||||
printf("%s: %s\n", get_text(TEXT_ENTER), get_text(TEXT_MAIN_MENU));
|
||||
return MENU_ERR_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 主菜单退出回调
|
||||
*/
|
||||
static MenuErrCode menu_exit_main(MenuNodeId node_id, struct MenuCoreCtx* core_ctx)
|
||||
{
|
||||
(void)node_id;
|
||||
(void)core_ctx;
|
||||
sg_is_menu_active = false;
|
||||
printf("%s: %s\n", get_text(TEXT_EXIT), get_text(TEXT_MAIN_MENU));
|
||||
return MENU_ERR_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 测量菜单进入回调
|
||||
*/
|
||||
static MenuErrCode menu_enter_item1(MenuNodeId node_id, struct MenuCoreCtx* core_ctx)
|
||||
{
|
||||
(void)node_id;
|
||||
(void)core_ctx;
|
||||
printf("%s: %s\n", get_text(TEXT_ENTER), get_text(TEXT_MAIN_MEASUREMENT));
|
||||
return MENU_ERR_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 保护菜单进入回调
|
||||
*/
|
||||
static MenuErrCode menu_enter_item2(MenuNodeId node_id, struct MenuCoreCtx* core_ctx)
|
||||
{
|
||||
(void)node_id;
|
||||
(void)core_ctx;
|
||||
printf("%s: %s\n", get_text(TEXT_ENTER), get_text(TEXT_MAIN_PROTECTION));
|
||||
return MENU_ERR_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 控制菜单进入回调
|
||||
*/
|
||||
static MenuErrCode menu_enter_item3(MenuNodeId node_id, struct MenuCoreCtx* core_ctx)
|
||||
{
|
||||
(void)node_id;
|
||||
(void)core_ctx;
|
||||
printf("%s: %s\n", get_text(TEXT_ENTER), get_text(TEXT_MAIN_CONTROL));
|
||||
return MENU_ERR_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 语言设置进入回调(声明)
|
||||
*/
|
||||
static MenuErrCode menu_enter_language(MenuNodeId node_id, struct MenuCoreCtx* core_ctx);
|
||||
|
||||
/**
|
||||
* @brief 设置菜单进入回调(声明)
|
||||
*/
|
||||
static MenuErrCode menu_enter_setting(MenuNodeId node_id, struct MenuCoreCtx* core_ctx);
|
||||
|
||||
/**
|
||||
* @brief 重新注册菜单节点,用于语言切换
|
||||
*/
|
||||
static void re_register_menu_nodes(void)
|
||||
{
|
||||
// 先注销所有节点
|
||||
menu_deinit();
|
||||
menu_init();
|
||||
|
||||
// 主菜单
|
||||
MenuNodeId main_node_id = menu_register_node(0, 0, get_text(TEXT_MAIN_MENU), menu_enter_main, menu_exit_main);
|
||||
|
||||
// 菜单项
|
||||
MenuNodeId item1_node_id = menu_register_node(0, main_node_id, get_text(TEXT_MAIN_MEASUREMENT), menu_enter_item1, NULL);
|
||||
MenuNodeId item2_node_id = menu_register_node(0, main_node_id, get_text(TEXT_MAIN_PROTECTION), menu_enter_item2, NULL);
|
||||
MenuNodeId item3_node_id = menu_register_node(0, main_node_id, get_text(TEXT_MAIN_CONTROL), menu_enter_item3, NULL);
|
||||
MenuNodeId sub1_node_id = menu_register_node(0, main_node_id, get_text(TEXT_MAIN_SETTING), menu_enter_setting, NULL);
|
||||
|
||||
// 测量菜单子项
|
||||
menu_register_node(0, item1_node_id, get_text(TEXT_MEASURE_VOLTAGE), NULL, NULL);
|
||||
menu_register_node(0, item1_node_id, get_text(TEXT_MEASURE_CURRENT), NULL, NULL);
|
||||
menu_register_node(0, item1_node_id, get_text(TEXT_MEASURE_POWER), NULL, NULL);
|
||||
menu_register_node(0, item1_node_id, get_text(TEXT_MEASURE_ENERGY), NULL, NULL);
|
||||
menu_register_node(0, item1_node_id, get_text(TEXT_MEASURE_FREQUENCY), NULL, NULL);
|
||||
menu_register_node(0, item1_node_id, get_text(TEXT_MEASURE_TEMPERATURE), NULL, NULL);
|
||||
|
||||
// 保护菜单子项
|
||||
menu_register_node(0, item2_node_id, get_text(TEXT_PROTECT_OVER_VOLTAGE), NULL, NULL);
|
||||
menu_register_node(0, item2_node_id, get_text(TEXT_PROTECT_UNDER_VOLTAGE), NULL, NULL);
|
||||
menu_register_node(0, item2_node_id, get_text(TEXT_PROTECT_OVER_CURRENT), NULL, NULL);
|
||||
menu_register_node(0, item2_node_id, get_text(TEXT_PROTECT_SETTING), NULL, NULL);
|
||||
|
||||
// 控制菜单子项
|
||||
menu_register_node(0, item3_node_id, get_text(TEXT_CONTROL_ON_OFF), NULL, NULL);
|
||||
menu_register_node(0, item3_node_id, get_text(TEXT_CONTROL_START_STOP), NULL, NULL);
|
||||
menu_register_node(0, item3_node_id, get_text(TEXT_CONTROL_AUTO_MANUAL), NULL, NULL);
|
||||
|
||||
// 设置菜单子项
|
||||
menu_register_node(0, sub1_node_id, get_text(TEXT_SETTING_LANGUAGE), menu_enter_language, NULL);
|
||||
|
||||
// 重新注册持久化回调函数
|
||||
menu_persistence_register_callback(demo_persistence_save, demo_persistence_restore);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 设置菜单进入回调
|
||||
*/
|
||||
static MenuErrCode menu_enter_setting(MenuNodeId node_id, struct MenuCoreCtx* core_ctx)
|
||||
{
|
||||
(void)node_id;
|
||||
(void)core_ctx;
|
||||
printf("%s: %s\n", get_text(TEXT_ENTER), get_text(TEXT_MAIN_SETTING));
|
||||
return MENU_ERR_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 语言设置进入回调
|
||||
*/
|
||||
static MenuErrCode menu_enter_language(MenuNodeId node_id, struct MenuCoreCtx* core_ctx)
|
||||
{
|
||||
(void)node_id;
|
||||
(void)core_ctx;
|
||||
printf("%s: %s\n", get_text(TEXT_ENTER), get_text(TEXT_SETTING_LANGUAGE));
|
||||
|
||||
// 切换语言
|
||||
if (sg_current_language == SYSTEM_LANG_CHINESE)
|
||||
{
|
||||
sg_current_language = SYSTEM_LANG_ENGLISH;
|
||||
set_language(SYSTEM_LANG_ENGLISH);
|
||||
printf("%s: English\n", get_text(TEXT_SETTING_LANGUAGE));
|
||||
}
|
||||
else
|
||||
{
|
||||
sg_current_language = SYSTEM_LANG_CHINESE;
|
||||
set_language(SYSTEM_LANG_CHINESE);
|
||||
printf("%s: 中文\n", get_text(TEXT_SETTING_LANGUAGE));
|
||||
}
|
||||
|
||||
// 重新注册菜单节点,更新菜单名称
|
||||
re_register_menu_nodes();
|
||||
|
||||
return MENU_ERR_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 默认获取系统时间函数
|
||||
*/
|
||||
static uint32_t demo_get_tick(void)
|
||||
{
|
||||
return sg_tick++;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 默认打印函数
|
||||
*/
|
||||
static void demo_printf(const char* fmt, va_list args)
|
||||
{
|
||||
vprintf(fmt, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 持久化数据保存回调函数
|
||||
* @param data 要保存的数据
|
||||
* @param size 数据大小
|
||||
* @return 错误码
|
||||
*/
|
||||
static MenuErrCode demo_persistence_save(const uint8_t* data, uint16_t size)
|
||||
{
|
||||
FILE* fp = NULL;
|
||||
MenuErrCode ret = MENU_ERR_OK;
|
||||
|
||||
// 打开文件进行写入
|
||||
fp = fopen("menu_persistence.dat", "wb");
|
||||
if (fp == NULL) {
|
||||
printf("%s: %s\n", get_text(TEXT_ERROR), "打开文件失败");
|
||||
return MENU_ERR_OPERATION_FAILED;
|
||||
}
|
||||
|
||||
// 写入数据
|
||||
if (fwrite(data, 1, size, fp) != size) {
|
||||
printf("%s: %s\n", get_text(TEXT_ERROR), "写入文件失败");
|
||||
ret = MENU_ERR_OPERATION_FAILED;
|
||||
}
|
||||
|
||||
// 关闭文件
|
||||
fclose(fp);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 持久化数据恢复回调函数
|
||||
* @param data 恢复数据缓冲区
|
||||
* @param size 数据大小
|
||||
* @return 错误码
|
||||
*/
|
||||
static MenuErrCode demo_persistence_restore(uint8_t* data, uint16_t* size)
|
||||
{
|
||||
FILE* fp = NULL;
|
||||
MenuErrCode ret = MENU_ERR_OK;
|
||||
size_t read_size = 0;
|
||||
|
||||
// 打开文件进行读取
|
||||
fp = fopen("menu_persistence.dat", "rb");
|
||||
if (fp == NULL) {
|
||||
printf("%s: %s\n", get_text(TEXT_WARNING), "文件不存在,使用默认设置");
|
||||
return MENU_ERR_OPERATION_FAILED;
|
||||
}
|
||||
|
||||
// 获取文件大小
|
||||
fseek(fp, 0, SEEK_END);
|
||||
read_size = ftell(fp);
|
||||
fseek(fp, 0, SEEK_SET);
|
||||
|
||||
// 检查文件大小是否合法
|
||||
if (read_size > *size) {
|
||||
printf("%s: %s\n", get_text(TEXT_ERROR), "文件过大");
|
||||
fclose(fp);
|
||||
return MENU_ERR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
// 读取数据
|
||||
if (fread(data, 1, read_size, fp) != read_size) {
|
||||
printf("%s: %s\n", get_text(TEXT_ERROR), "读取文件失败");
|
||||
ret = MENU_ERR_OPERATION_FAILED;
|
||||
} else {
|
||||
// 更新实际读取的大小
|
||||
*size = (uint16_t)read_size;
|
||||
}
|
||||
|
||||
// 关闭文件
|
||||
fclose(fp);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 硬件驱动配置
|
||||
*/
|
||||
static MenuPortDriver sg_demo_driver = {
|
||||
.printf = demo_printf,
|
||||
.get_tick = demo_get_tick,
|
||||
.delay_ms = NULL,
|
||||
.display = NULL,
|
||||
.key_scan = NULL,
|
||||
.irq_ctrl = NULL,
|
||||
.error_handler = NULL,
|
||||
.modbus_send = NULL,
|
||||
.modbus_receive = NULL,
|
||||
.persistence_save = demo_persistence_save,
|
||||
.persistence_restore = demo_persistence_restore,
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 演示菜单导航
|
||||
*/
|
||||
/**
|
||||
* @brief 显示当前菜单的子菜单项
|
||||
*/
|
||||
static void display_menu_items(void)
|
||||
{
|
||||
MenuNodeId curr_node_id = menu_get_current_node();
|
||||
const MenuNode* curr_node = menu_find_node(curr_node_id);
|
||||
|
||||
if (curr_node == NULL) {
|
||||
printf("\n无效节点\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// 清屏(可选,根据终端支持情况)
|
||||
printf("\033[2J\033[H");
|
||||
|
||||
// 显示当前菜单路径
|
||||
printf("%s\n", get_text(TEXT_MAIN_MENU));
|
||||
printf("==================\n");
|
||||
|
||||
// 显示当前菜单状态
|
||||
printf("%s: %s\n", get_text(TEXT_CURRENT), curr_node->name ? curr_node->name : "Unnamed");
|
||||
|
||||
// 显示导航路径
|
||||
uint8_t path_depth = menu_get_nav_depth();
|
||||
MenuNodeId path[MENU_CONFIG_STACK_DEPTH];
|
||||
menu_get_nav_path(path, MENU_CONFIG_STACK_DEPTH);
|
||||
|
||||
printf("%s: ", get_text(TEXT_STATUS));
|
||||
for (uint8_t i = 0; i < path_depth; i++) {
|
||||
const MenuNode* path_node = menu_find_node(path[i]);
|
||||
if (path_node != NULL) {
|
||||
printf("%s", path_node->name ? path_node->name : "Unnamed");
|
||||
if (i < path_depth - 1) {
|
||||
printf(" -> ");
|
||||
}
|
||||
}
|
||||
}
|
||||
printf("\n\n");
|
||||
|
||||
// 显示当前菜单的子菜单项
|
||||
printf("%s:\n", get_text(TEXT_SUMMARY));
|
||||
|
||||
if (curr_node->first_child_id != 0) {
|
||||
// 遍历所有子节点
|
||||
const MenuNode* child_node = menu_find_node(curr_node->first_child_id);
|
||||
uint8_t item_index = 1;
|
||||
|
||||
while (child_node != NULL) {
|
||||
// 子菜单仅显示列表,不高亮选中项(高亮在同级节点中处理)
|
||||
printf(" %d. %s\n", item_index, child_node->name ? child_node->name : "Unnamed");
|
||||
|
||||
MenuNodeId next_id = child_node->next_sibling_id;
|
||||
if (next_id == 0) {
|
||||
break;
|
||||
}
|
||||
child_node = menu_find_node(next_id);
|
||||
item_index++;
|
||||
}
|
||||
} else {
|
||||
printf(" %s\n", get_text(TEXT_EMPTY));
|
||||
}
|
||||
|
||||
// 显示当前节点在同级节点中的位置
|
||||
const MenuNode* parent_node = menu_find_node(curr_node->parent_id);
|
||||
if (parent_node != NULL) {
|
||||
printf("\n%s:\n", get_text(TEXT_CURRENT_SELECTION));
|
||||
|
||||
// 遍历所有兄弟节点
|
||||
const MenuNode* sibling_node = menu_find_node(parent_node->first_child_id);
|
||||
uint8_t item_index = 1;
|
||||
|
||||
while (sibling_node != NULL) {
|
||||
// 检查当前显示的节点是否为选中节点
|
||||
bool is_selected = (sibling_node->id == curr_node_id);
|
||||
|
||||
// 高亮显示当前选中的菜单项
|
||||
if (is_selected) {
|
||||
printf(" **> %d. %s <**\n",
|
||||
item_index, sibling_node->name ? sibling_node->name : "Unnamed");
|
||||
} else {
|
||||
printf(" %d. %s\n", item_index, sibling_node->name ? sibling_node->name : "Unnamed");
|
||||
}
|
||||
|
||||
MenuNodeId next_id = sibling_node->next_sibling_id;
|
||||
if (next_id == 0) {
|
||||
break;
|
||||
}
|
||||
sibling_node = menu_find_node(next_id);
|
||||
item_index++;
|
||||
}
|
||||
}
|
||||
|
||||
// 显示菜单操作提示
|
||||
printf("\n%s:\n", get_text(TEXT_OPERATIONS));
|
||||
printf(" ↑/%s: %s\n", get_text(TEXT_DOWN_ARROW), get_text(TEXT_SELECT_ITEM));
|
||||
printf(" %s: %s\n", get_text(TEXT_ENTER), get_text(TEXT_ENTER_SUBMENU));
|
||||
printf(" Esc/%s: %s\n", get_text(TEXT_LEFT_ARROW), get_text(TEXT_RETURN_PARENT));
|
||||
printf(" q: %s\n", get_text(TEXT_EXIT));
|
||||
printf("==================\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 演示菜单导航
|
||||
*/
|
||||
static void demo_menu_navigation(void)
|
||||
{
|
||||
char key = 0;
|
||||
|
||||
// 设置终端为无缓冲模式(仅在非Windows平台)
|
||||
#ifndef _WIN32
|
||||
set_nonblocking_terminal();
|
||||
#endif
|
||||
|
||||
printf("\n%s\n", get_text(TEXT_MAIN_MENU));
|
||||
printf("==================\n");
|
||||
printf("%s: %s\n", get_text(TEXT_SELECT_OPTION), get_text(TEXT_MAIN_MENU));
|
||||
printf("Enter: %s\n", get_text(TEXT_ENTER));
|
||||
printf("Esc: %s\n", get_text(TEXT_EXIT));
|
||||
printf("q: %s\n", get_text(TEXT_EXIT));
|
||||
printf("%s: %s\n", get_text(TEXT_UP_ARROW), get_text(TEXT_SELECT_PREVIOUS));
|
||||
printf("%s: %s\n", get_text(TEXT_DOWN_ARROW), get_text(TEXT_SELECT_NEXT));
|
||||
printf("%s: %s\n", get_text(TEXT_RIGHT_ARROW), get_text(TEXT_ENTER_SUBMENU));
|
||||
printf("%s: %s\n", get_text(TEXT_LEFT_ARROW), get_text(TEXT_RETURN_PARENT));
|
||||
printf("==================\n");
|
||||
|
||||
// 初始化显示
|
||||
display_menu_items();
|
||||
|
||||
while (1) {
|
||||
// 持续处理菜单主循环,确保事件得到及时处理
|
||||
menu_main_loop(demo_get_tick());
|
||||
|
||||
// 检查是否有按键输入
|
||||
if (IS_KEY_AVAILABLE()) {
|
||||
// 获取按键输入(无缓冲)
|
||||
key = NON_BLOCKING_GETCHAR();
|
||||
|
||||
// 处理退出键
|
||||
if (key == 'q' || key == 'Q') {
|
||||
#ifndef _WIN32
|
||||
restore_terminal();
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
// 处理回车键
|
||||
else if (key == '\n' || key == '\r') {
|
||||
// 不管节点是否有子菜单,都处理回车事件
|
||||
menu_post_event(MENU_EVENT_KEY_ENTER, 1);
|
||||
|
||||
// 处理菜单主循环
|
||||
menu_main_loop(demo_get_tick());
|
||||
|
||||
// 显示当前菜单状态和菜单项
|
||||
display_menu_items();
|
||||
}
|
||||
// 处理Esc键和箭头键
|
||||
else if (key == '\x1B') {
|
||||
// 读取完整的Esc序列
|
||||
char esc_key = NON_BLOCKING_GETCHAR();
|
||||
if (esc_key == '[') {
|
||||
key = NON_BLOCKING_GETCHAR();
|
||||
switch (key) {
|
||||
case 'A': // 上箭头 - 选择上一项
|
||||
menu_post_event(MENU_EVENT_KEY_UP, 1);
|
||||
break;
|
||||
case 'B': // 下箭头 - 选择下一项
|
||||
menu_post_event(MENU_EVENT_KEY_DOWN, 1);
|
||||
break;
|
||||
case 'C': // 右箭头 - 进入子菜单
|
||||
menu_post_event(MENU_EVENT_KEY_ENTER, 1);
|
||||
break;
|
||||
case 'D': // 左箭头 - 返回上一级菜单
|
||||
menu_post_event(MENU_EVENT_KEY_ESC, 1);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// 处理菜单主循环
|
||||
menu_main_loop(demo_get_tick());
|
||||
|
||||
// 显示当前菜单状态和菜单项
|
||||
display_menu_items();
|
||||
} else {
|
||||
// 单独的Esc键 - 返回上一级菜单
|
||||
menu_post_event(MENU_EVENT_KEY_ESC, 1);
|
||||
|
||||
// 处理菜单主循环
|
||||
menu_main_loop(demo_get_tick());
|
||||
|
||||
// 显示当前菜单状态和菜单项
|
||||
display_menu_items();
|
||||
}
|
||||
}
|
||||
// 处理普通字符输入
|
||||
else {
|
||||
// 可以根据需要添加其他字符处理逻辑
|
||||
}
|
||||
}
|
||||
|
||||
// 短延迟,避免CPU占用过高
|
||||
#ifdef _WIN32
|
||||
Sleep(10);
|
||||
#else
|
||||
usleep(10000); // 10ms
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 主函数
|
||||
*/
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
(void)argc;
|
||||
(void)argv;
|
||||
|
||||
// 初始化多语言系统
|
||||
set_language(SYSTEM_LANG_CHINESE);
|
||||
|
||||
printf("%s\n", get_text(TEXT_MAIN_MENU));
|
||||
printf("==================\n");
|
||||
|
||||
// 初始化硬件端口层
|
||||
menu_port_init(&sg_demo_driver);
|
||||
|
||||
// 初始化菜单组件
|
||||
MenuErrCode err = menu_init();
|
||||
if (err != MENU_ERR_OK) {
|
||||
printf("%s\n", get_text(TEXT_TEST));
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
// 注册菜单节点
|
||||
// 主菜单
|
||||
MenuNodeId main_node_id = menu_register_node(0, 0, get_text(TEXT_MAIN_MENU), menu_enter_main, menu_exit_main);
|
||||
|
||||
// 菜单项
|
||||
MenuNodeId item1_node_id = menu_register_node(0, main_node_id, get_text(TEXT_MAIN_MEASUREMENT), menu_enter_item1, NULL);
|
||||
MenuNodeId item2_node_id = menu_register_node(0, main_node_id, get_text(TEXT_MAIN_PROTECTION), menu_enter_item2, NULL);
|
||||
MenuNodeId item3_node_id = menu_register_node(0, main_node_id, get_text(TEXT_MAIN_CONTROL), menu_enter_item3, NULL);
|
||||
MenuNodeId sub1_node_id = menu_register_node(0, main_node_id, get_text(TEXT_MAIN_SETTING), menu_enter_setting, NULL);
|
||||
|
||||
// 测量菜单子项
|
||||
menu_register_node(0, item1_node_id, get_text(TEXT_MEASURE_VOLTAGE), NULL, NULL);
|
||||
menu_register_node(0, item1_node_id, get_text(TEXT_MEASURE_CURRENT), NULL, NULL);
|
||||
menu_register_node(0, item1_node_id, get_text(TEXT_MEASURE_POWER), NULL, NULL);
|
||||
menu_register_node(0, item1_node_id, get_text(TEXT_MEASURE_ENERGY), NULL, NULL);
|
||||
menu_register_node(0, item1_node_id, get_text(TEXT_MEASURE_FREQUENCY), NULL, NULL);
|
||||
menu_register_node(0, item1_node_id, get_text(TEXT_MEASURE_TEMPERATURE), NULL, NULL);
|
||||
|
||||
// 保护菜单子项
|
||||
menu_register_node(0, item2_node_id, get_text(TEXT_PROTECT_OVER_VOLTAGE), NULL, NULL);
|
||||
menu_register_node(0, item2_node_id, get_text(TEXT_PROTECT_UNDER_VOLTAGE), NULL, NULL);
|
||||
menu_register_node(0, item2_node_id, get_text(TEXT_PROTECT_OVER_CURRENT), NULL, NULL);
|
||||
menu_register_node(0, item2_node_id, get_text(TEXT_PROTECT_SETTING), NULL, NULL);
|
||||
|
||||
// 控制菜单子项
|
||||
menu_register_node(0, item3_node_id, get_text(TEXT_CONTROL_ON_OFF), NULL, NULL);
|
||||
menu_register_node(0, item3_node_id, get_text(TEXT_CONTROL_START_STOP), NULL, NULL);
|
||||
menu_register_node(0, item3_node_id, get_text(TEXT_CONTROL_AUTO_MANUAL), NULL, NULL);
|
||||
|
||||
// 设置菜单子项
|
||||
menu_register_node(0, sub1_node_id, get_text(TEXT_SETTING_LANGUAGE), menu_enter_language, NULL);
|
||||
|
||||
// 注册持久化回调函数
|
||||
err = menu_persistence_register_callback(demo_persistence_save, demo_persistence_restore);
|
||||
if (err != MENU_ERR_OK) {
|
||||
printf("%s: %s\n", get_text(TEXT_ERROR), get_text(TEXT_REGISTER_CALLBACK_FAILED));
|
||||
}
|
||||
|
||||
// 尝试恢复之前的菜单状态
|
||||
err = menu_persistence_restore();
|
||||
if (err == MENU_ERR_OK) {
|
||||
printf("%s: %s\n", get_text(TEXT_SUCCESS), get_text(TEXT_RESTORE_MENU_STATE));
|
||||
} else {
|
||||
printf("%s: %s: Error %d\n", get_text(TEXT_ERROR), get_text(TEXT_RESTORE_MENU_STATE_FAILED), err);
|
||||
printf("%s\n", get_text(TEXT_START_DEFAULT));
|
||||
}
|
||||
|
||||
printf("%s\n", get_text(TEXT_TEST));
|
||||
|
||||
// 运行菜单演示
|
||||
demo_menu_navigation();
|
||||
|
||||
// 保存当前菜单状态
|
||||
err = menu_persistence_save();
|
||||
if (err == MENU_ERR_OK) {
|
||||
printf("%s: %s\n", get_text(TEXT_SUCCESS), get_text(TEXT_SAVE_MENU_STATE));
|
||||
} else {
|
||||
printf("%s: %s: Error %d\n", get_text(TEXT_ERROR), get_text(TEXT_SAVE_MENU_STATE_FAILED), err);
|
||||
}
|
||||
|
||||
// 清理资源
|
||||
menu_deinit();
|
||||
menu_port_deinit();
|
||||
|
||||
printf("\n%s\n", get_text(TEXT_EXIT));
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
525
demo/language.c
525
demo/language.c
@ -1,525 +0,0 @@
|
||||
#include "language.h"
|
||||
|
||||
/* 全局变量定义 ---------------------------------------------------------------------------------------------------*/
|
||||
|
||||
/**
|
||||
* @brief 当前系统语言
|
||||
*/
|
||||
static SystemLang_e sg_current_language = SYSTEM_LANG_CHINESE;
|
||||
|
||||
/**
|
||||
* @brief 中文文本数组
|
||||
*/
|
||||
static const char *sg_chinese_text[] = {
|
||||
[TEXT_ENTER] = "进入",
|
||||
[TEXT_EXIT] = "退出",
|
||||
[TEXT_RETURN] = "返回",
|
||||
[TEXT_OK] = "确定",
|
||||
[TEXT_CANCEL] = "取消",
|
||||
[TEXT_SAVE] = "保存",
|
||||
[TEXT_LOAD] = "加载",
|
||||
|
||||
[TEXT_MAIN_MENU] = "主菜单",
|
||||
[TEXT_MAIN_MEASUREMENT] = "测量",
|
||||
[TEXT_MAIN_PROTECTION] = "保护",
|
||||
[TEXT_MAIN_CONTROL] = "控制",
|
||||
[TEXT_MAIN_SETTING] = "设置",
|
||||
[TEXT_MAIN_RECORD] = "记录",
|
||||
[TEXT_MAIN_HELP] = "帮助",
|
||||
[TEXT_MAIN_ABOUT] = "关于",
|
||||
|
||||
[TEXT_MEASURE_VOLTAGE] = "电压",
|
||||
[TEXT_MEASURE_CURRENT] = "电流",
|
||||
[TEXT_MEASURE_POWER] = "功率",
|
||||
[TEXT_MEASURE_ENERGY] = "能量",
|
||||
[TEXT_MEASURE_FREQUENCY] = "频率",
|
||||
[TEXT_MEASURE_TEMPERATURE] = "温度",
|
||||
|
||||
[TEXT_PROTECT_OVER_VOLTAGE] = "过电压保护",
|
||||
[TEXT_PROTECT_UNDER_VOLTAGE] = "欠电压保护",
|
||||
[TEXT_PROTECT_OVER_CURRENT] = "过电流保护",
|
||||
[TEXT_PROTECT_SETTING] = "保护设置",
|
||||
|
||||
[TEXT_CONTROL_ON_OFF] = "开关控制",
|
||||
[TEXT_CONTROL_START_STOP] = "启停控制",
|
||||
[TEXT_CONTROL_AUTO_MANUAL] = "自动/手动",
|
||||
|
||||
[TEXT_SETTING_LANGUAGE] = "语言设置",
|
||||
|
||||
[TEXT_RECORD_EVENT] = "事件记录",
|
||||
|
||||
[TEXT_HELP_GUIDE] = "指南",
|
||||
|
||||
[TEXT_ABOUT_VERSION] = "版本信息",
|
||||
|
||||
[TEXT_PERSIST_SAVE_STATE] = "保存菜单状态",
|
||||
[TEXT_PERSIST_RESTORE_STATE] = "恢复菜单状态",
|
||||
[TEXT_PERSIST_SAVE_SUCCESS] = "保存成功",
|
||||
[TEXT_PERSIST_SAVE_FAILED] = "保存失败",
|
||||
[TEXT_PERSIST_RESTORE_SUCCESS] = "恢复成功",
|
||||
[TEXT_PERSIST_RESTORE_FAILED] = "恢复失败",
|
||||
|
||||
[TEXT_ERROR] = "错误",
|
||||
[TEXT_WARNING] = "警告",
|
||||
[TEXT_SUCCESS] = "成功",
|
||||
[TEXT_INFO] = "信息",
|
||||
[TEXT_TEST] = "测试",
|
||||
[TEXT_SELECT_OPTION] = "选择操作",
|
||||
[TEXT_CURRENT] = "当前",
|
||||
[TEXT_STATUS] = "状态",
|
||||
[TEXT_SUMMARY] = "摘要",
|
||||
[TEXT_EMPTY] = "空",
|
||||
[TEXT_CURRENT_SELECTION] = "当前选择",
|
||||
[TEXT_OPERATIONS] = "操作",
|
||||
[TEXT_SELECT_ITEM] = "选择项",
|
||||
[TEXT_ENTER_SUBMENU] = "进入子菜单",
|
||||
[TEXT_RETURN_PARENT] = "返回父菜单",
|
||||
[TEXT_SELECT_PREVIOUS] = "选择上一项",
|
||||
[TEXT_SELECT_NEXT] = "选择下一项",
|
||||
[TEXT_START_DEFAULT] = "从默认开始",
|
||||
[TEXT_REGISTER_CALLBACK_FAILED] = "注册回调失败",
|
||||
[TEXT_RESTORE_MENU_STATE] = "恢复菜单状态",
|
||||
[TEXT_RESTORE_MENU_STATE_FAILED] = "恢复菜单状态失败",
|
||||
[TEXT_SAVE_MENU_STATE_FAILED] = "保存菜单状态失败",
|
||||
|
||||
[TEXT_UP_ARROW] = "上箭头",
|
||||
[TEXT_DOWN_ARROW] = "下箭头",
|
||||
[TEXT_LEFT_ARROW] = "左箭头",
|
||||
[TEXT_RIGHT_ARROW] = "右箭头",
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 英文文本数组
|
||||
*/
|
||||
static const char *sg_english_text[] = {
|
||||
[TEXT_ENTER] = "Enter",
|
||||
[TEXT_EXIT] = "Exit",
|
||||
[TEXT_RETURN] = "Return",
|
||||
[TEXT_OK] = "OK",
|
||||
[TEXT_CANCEL] = "Cancel",
|
||||
[TEXT_SAVE] = "Save",
|
||||
[TEXT_LOAD] = "Load",
|
||||
|
||||
[TEXT_MAIN_MENU] = "Main Menu",
|
||||
[TEXT_MAIN_MEASUREMENT] = "Measurement",
|
||||
[TEXT_MAIN_PROTECTION] = "Protection",
|
||||
[TEXT_MAIN_CONTROL] = "Control",
|
||||
[TEXT_MAIN_SETTING] = "Setting",
|
||||
[TEXT_MAIN_RECORD] = "Record",
|
||||
[TEXT_MAIN_HELP] = "Help",
|
||||
[TEXT_MAIN_ABOUT] = "About",
|
||||
|
||||
[TEXT_MEASURE_VOLTAGE] = "Voltage",
|
||||
[TEXT_MEASURE_CURRENT] = "Current",
|
||||
[TEXT_MEASURE_POWER] = "Power",
|
||||
[TEXT_MEASURE_ENERGY] = "Energy",
|
||||
[TEXT_MEASURE_FREQUENCY] = "Frequency",
|
||||
[TEXT_MEASURE_TEMPERATURE] = "Temperature",
|
||||
|
||||
[TEXT_PROTECT_OVER_VOLTAGE] = "Over Voltage Protection",
|
||||
[TEXT_PROTECT_UNDER_VOLTAGE] = "Under Voltage Protection",
|
||||
[TEXT_PROTECT_OVER_CURRENT] = "Over Current Protection",
|
||||
[TEXT_PROTECT_SETTING] = "Protection Setting",
|
||||
|
||||
[TEXT_CONTROL_ON_OFF] = "On/Off",
|
||||
[TEXT_CONTROL_START_STOP] = "Start/Stop",
|
||||
[TEXT_CONTROL_AUTO_MANUAL] = "Auto/Manual",
|
||||
|
||||
[TEXT_SETTING_LANGUAGE] = "Language Setting",
|
||||
|
||||
[TEXT_RECORD_EVENT] = "Event Record",
|
||||
|
||||
[TEXT_HELP_GUIDE] = "Guide",
|
||||
|
||||
[TEXT_ABOUT_VERSION] = "Version Info",
|
||||
|
||||
[TEXT_PERSIST_SAVE_STATE] = "Save Menu State",
|
||||
[TEXT_PERSIST_RESTORE_STATE] = "Restore Menu State",
|
||||
[TEXT_PERSIST_SAVE_SUCCESS] = "Save Success",
|
||||
[TEXT_PERSIST_SAVE_FAILED] = "Save Failed",
|
||||
[TEXT_PERSIST_RESTORE_SUCCESS] = "Restore Success",
|
||||
[TEXT_PERSIST_RESTORE_FAILED] = "Restore Failed",
|
||||
|
||||
[TEXT_ERROR] = "Error",
|
||||
[TEXT_WARNING] = "Warning",
|
||||
[TEXT_SUCCESS] = "Success",
|
||||
[TEXT_INFO] = "Info",
|
||||
[TEXT_TEST] = "Test",
|
||||
[TEXT_SELECT_OPTION] = "Select Option",
|
||||
[TEXT_CURRENT] = "Current",
|
||||
[TEXT_STATUS] = "Status",
|
||||
[TEXT_SUMMARY] = "Summary",
|
||||
[TEXT_EMPTY] = "Empty",
|
||||
[TEXT_CURRENT_SELECTION] = "Current Selection",
|
||||
[TEXT_OPERATIONS] = "Operations",
|
||||
[TEXT_SELECT_ITEM] = "Select Item",
|
||||
[TEXT_ENTER_SUBMENU] = "Enter Submenu",
|
||||
[TEXT_RETURN_PARENT] = "Return Parent",
|
||||
[TEXT_SELECT_PREVIOUS] = "Select Previous",
|
||||
[TEXT_SELECT_NEXT] = "Select Next",
|
||||
[TEXT_START_DEFAULT] = "Start Default",
|
||||
[TEXT_REGISTER_CALLBACK_FAILED] = "Register Callback Failed",
|
||||
[TEXT_RESTORE_MENU_STATE] = "Restore Menu State",
|
||||
[TEXT_RESTORE_MENU_STATE_FAILED] = "Restore Menu State Failed",
|
||||
[TEXT_SAVE_MENU_STATE_FAILED] = "Save Menu State Failed",
|
||||
|
||||
[TEXT_UP_ARROW] = "Up Arrow",
|
||||
[TEXT_DOWN_ARROW] = "Down Arrow",
|
||||
[TEXT_LEFT_ARROW] = "Left Arrow",
|
||||
[TEXT_RIGHT_ARROW] = "Right Arrow",
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 日语文本数组
|
||||
*/
|
||||
static const char *sg_japanese_text[] = {
|
||||
[TEXT_ENTER] = "入る",
|
||||
[TEXT_EXIT] = "退出",
|
||||
[TEXT_RETURN] = "戻る",
|
||||
[TEXT_OK] = "OK",
|
||||
[TEXT_CANCEL] = "キャンセル",
|
||||
[TEXT_SAVE] = "保存",
|
||||
[TEXT_LOAD] = "読み込む",
|
||||
|
||||
[TEXT_MAIN_MENU] = "メインメニュー",
|
||||
[TEXT_MAIN_MEASUREMENT] = "計測",
|
||||
[TEXT_MAIN_PROTECTION] = "保護",
|
||||
[TEXT_MAIN_CONTROL] = "制御",
|
||||
[TEXT_MAIN_SETTING] = "設定",
|
||||
[TEXT_MAIN_RECORD] = "記録",
|
||||
[TEXT_MAIN_HELP] = "ヘルプ",
|
||||
[TEXT_MAIN_ABOUT] = "について",
|
||||
|
||||
[TEXT_MEASURE_VOLTAGE] = "電圧",
|
||||
[TEXT_MEASURE_CURRENT] = "電流",
|
||||
[TEXT_MEASURE_POWER] = "電力",
|
||||
[TEXT_MEASURE_ENERGY] = "エネルギー",
|
||||
[TEXT_MEASURE_FREQUENCY] = "周波数",
|
||||
[TEXT_MEASURE_TEMPERATURE] = "温度",
|
||||
|
||||
[TEXT_PROTECT_OVER_VOLTAGE] = "過電圧保護",
|
||||
[TEXT_PROTECT_UNDER_VOLTAGE] = "不足電圧保護",
|
||||
[TEXT_PROTECT_OVER_CURRENT] = "過電流保護",
|
||||
[TEXT_PROTECT_SETTING] = "保護設定",
|
||||
|
||||
[TEXT_CONTROL_ON_OFF] = "オン/オフ",
|
||||
[TEXT_CONTROL_START_STOP] = "スタート/ストップ",
|
||||
[TEXT_CONTROL_AUTO_MANUAL] = "オート/マニュアル",
|
||||
|
||||
[TEXT_SETTING_LANGUAGE] = "言語設定",
|
||||
|
||||
[TEXT_RECORD_EVENT] = "イベント記録",
|
||||
|
||||
[TEXT_HELP_GUIDE] = "ガイド",
|
||||
|
||||
[TEXT_ABOUT_VERSION] = "バージョン情報",
|
||||
|
||||
[TEXT_PERSIST_SAVE_STATE] = "メニュー状態を保存",
|
||||
[TEXT_PERSIST_RESTORE_STATE] = "メニュー状態を復元",
|
||||
[TEXT_PERSIST_SAVE_SUCCESS] = "保存成功",
|
||||
[TEXT_PERSIST_SAVE_FAILED] = "保存失敗",
|
||||
[TEXT_PERSIST_RESTORE_SUCCESS] = "復元成功",
|
||||
[TEXT_PERSIST_RESTORE_FAILED] = "復元失敗",
|
||||
|
||||
[TEXT_ERROR] = "エラー",
|
||||
[TEXT_WARNING] = "警告",
|
||||
[TEXT_SUCCESS] = "成功",
|
||||
[TEXT_INFO] = "情報",
|
||||
[TEXT_TEST] = "テスト",
|
||||
[TEXT_SELECT_OPTION] = "オプションを選択",
|
||||
[TEXT_CURRENT] = "現在",
|
||||
[TEXT_STATUS] = "ステータス",
|
||||
[TEXT_SUMMARY] = "サマリー",
|
||||
[TEXT_EMPTY] = "空",
|
||||
[TEXT_CURRENT_SELECTION] = "現在の選択",
|
||||
[TEXT_OPERATIONS] = "操作",
|
||||
[TEXT_SELECT_ITEM] = "項目を選択",
|
||||
[TEXT_ENTER_SUBMENU] = "サブメニューに入る",
|
||||
[TEXT_RETURN_PARENT] = "親メニューに戻る",
|
||||
[TEXT_SELECT_PREVIOUS] = "前の項目を選択",
|
||||
[TEXT_SELECT_NEXT] = "次の項目を選択",
|
||||
[TEXT_START_DEFAULT] = "デフォルトから開始",
|
||||
[TEXT_REGISTER_CALLBACK_FAILED] = "コールバック登録失敗",
|
||||
[TEXT_RESTORE_MENU_STATE] = "メニュー状態を復元",
|
||||
[TEXT_RESTORE_MENU_STATE_FAILED] = "メニュー状態復元失敗",
|
||||
[TEXT_SAVE_MENU_STATE_FAILED] = "メニュー状態保存失敗",
|
||||
|
||||
[TEXT_UP_ARROW] = "上矢印",
|
||||
[TEXT_DOWN_ARROW] = "下矢印",
|
||||
[TEXT_LEFT_ARROW] = "左矢印",
|
||||
[TEXT_RIGHT_ARROW] = "右矢印",
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 德语文本数组
|
||||
*/
|
||||
static const char *sg_german_text[] = {
|
||||
[TEXT_ENTER] = "Eingeben",
|
||||
[TEXT_EXIT] = "Verlassen",
|
||||
[TEXT_RETURN] = "Zurück",
|
||||
[TEXT_OK] = "OK",
|
||||
[TEXT_CANCEL] = "Abbrechen",
|
||||
[TEXT_SAVE] = "Speichern",
|
||||
[TEXT_LOAD] = "Laden",
|
||||
|
||||
[TEXT_MAIN_MENU] = "Hauptmenü",
|
||||
[TEXT_MAIN_MEASUREMENT] = "Messung",
|
||||
[TEXT_MAIN_PROTECTION] = "Schutz",
|
||||
[TEXT_MAIN_CONTROL] = "Steuerung",
|
||||
[TEXT_MAIN_SETTING] = "Einstellung",
|
||||
[TEXT_MAIN_RECORD] = "Aufzeichnung",
|
||||
[TEXT_MAIN_HELP] = "Hilfe",
|
||||
[TEXT_MAIN_ABOUT] = "Über",
|
||||
|
||||
[TEXT_MEASURE_VOLTAGE] = "Spannung",
|
||||
[TEXT_MEASURE_CURRENT] = "Strom",
|
||||
[TEXT_MEASURE_POWER] = "Leistung",
|
||||
[TEXT_MEASURE_ENERGY] = "Energie",
|
||||
[TEXT_MEASURE_FREQUENCY] = "Frequenz",
|
||||
[TEXT_MEASURE_TEMPERATURE] = "Temperatur",
|
||||
|
||||
[TEXT_PROTECT_OVER_VOLTAGE] = "Überspannungsschutz",
|
||||
[TEXT_PROTECT_UNDER_VOLTAGE] = "Unterspannungsschutz",
|
||||
[TEXT_PROTECT_OVER_CURRENT] = "Überstromschutz",
|
||||
[TEXT_PROTECT_SETTING] = "Schutz-Einstellung",
|
||||
|
||||
[TEXT_CONTROL_ON_OFF] = "Ein/Aus",
|
||||
[TEXT_CONTROL_START_STOP] = "Start/Stopp",
|
||||
[TEXT_CONTROL_AUTO_MANUAL] = "Auto/Manuell",
|
||||
|
||||
[TEXT_SETTING_LANGUAGE] = "Spracheinstellung",
|
||||
|
||||
[TEXT_RECORD_EVENT] = "Ereignisaufzeichnung",
|
||||
|
||||
[TEXT_HELP_GUIDE] = "Anleitung",
|
||||
|
||||
[TEXT_ABOUT_VERSION] = "Versionsinfo",
|
||||
|
||||
[TEXT_PERSIST_SAVE_STATE] = "Menüzustand speichern",
|
||||
[TEXT_PERSIST_RESTORE_STATE] = "Menüzustand wiederherstellen",
|
||||
[TEXT_PERSIST_SAVE_SUCCESS] = "Speichern erfolgreich",
|
||||
[TEXT_PERSIST_SAVE_FAILED] = "Speichern fehlgeschlagen",
|
||||
[TEXT_PERSIST_RESTORE_SUCCESS] = "Wiederherstellen erfolgreich",
|
||||
[TEXT_PERSIST_RESTORE_FAILED] = "Wiederherstellen fehlgeschlagen",
|
||||
|
||||
[TEXT_ERROR] = "Fehler",
|
||||
[TEXT_WARNING] = "Warnung",
|
||||
[TEXT_SUCCESS] = "Erfolg",
|
||||
[TEXT_INFO] = "Info",
|
||||
[TEXT_TEST] = "Test",
|
||||
[TEXT_SELECT_OPTION] = "Option wählen",
|
||||
[TEXT_CURRENT] = "Aktuell",
|
||||
[TEXT_STATUS] = "Status",
|
||||
[TEXT_SUMMARY] = "Zusammenfassung",
|
||||
[TEXT_EMPTY] = "Leer",
|
||||
[TEXT_CURRENT_SELECTION] = "Aktuelle Auswahl",
|
||||
[TEXT_OPERATIONS] = "Operationen",
|
||||
[TEXT_SELECT_ITEM] = "Element auswählen",
|
||||
[TEXT_ENTER_SUBMENU] = "In Untermenü eintreten",
|
||||
[TEXT_RETURN_PARENT] = "Zu übergeordnetem Menü zurückkehren",
|
||||
[TEXT_SELECT_PREVIOUS] = "Vorheriges auswählen",
|
||||
[TEXT_SELECT_NEXT] = "Nächstes auswählen",
|
||||
[TEXT_START_DEFAULT] = "Standard starten",
|
||||
[TEXT_REGISTER_CALLBACK_FAILED] = "Rückrufregistrierung fehlgeschlagen",
|
||||
[TEXT_RESTORE_MENU_STATE] = "Menüzustand wiederherstellen",
|
||||
[TEXT_RESTORE_MENU_STATE_FAILED] = "Wiederherstellung des Menüzustands fehlgeschlagen",
|
||||
[TEXT_SAVE_MENU_STATE_FAILED] = "Speichern des Menüzustands fehlgeschlagen",
|
||||
|
||||
[TEXT_UP_ARROW] = "Pfeil nach oben",
|
||||
[TEXT_DOWN_ARROW] = "Pfeil nach unten",
|
||||
[TEXT_LEFT_ARROW] = "Pfeil nach links",
|
||||
[TEXT_RIGHT_ARROW] = "Pfeil nach rechts",
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 语言文本映射表
|
||||
*/
|
||||
static const char **sg_language_text_map[] = {
|
||||
[SYSTEM_LANG_CHINESE] = sg_chinese_text,
|
||||
[SYSTEM_LANG_ENGLISH] = sg_english_text,
|
||||
[SYSTEM_LANG_JAPANESE] = sg_japanese_text,
|
||||
[SYSTEM_LANG_GERMAN] = sg_german_text,
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 全局菜单节点映射表
|
||||
*/
|
||||
const MenuNodeInfo_t menu_node_map[] = {
|
||||
// 一级菜单
|
||||
{MENU_NODE_ID_MAIN, MENU_NODE_ID_NONE, TEXT_MAIN_MENU, 1, MENU_NODE_ATTR_HAS_CHILDREN | MENU_NODE_ATTR_VISIBLE},
|
||||
{MENU_NODE_ID_MEASUREMENT, MENU_NODE_ID_MAIN, TEXT_MAIN_MEASUREMENT, 1, MENU_NODE_ATTR_HAS_CHILDREN | MENU_NODE_ATTR_VISIBLE},
|
||||
{MENU_NODE_ID_PROTECTION, MENU_NODE_ID_MAIN, TEXT_MAIN_PROTECTION, 1, MENU_NODE_ATTR_HAS_CHILDREN | MENU_NODE_ATTR_VISIBLE},
|
||||
{MENU_NODE_ID_CONTROL, MENU_NODE_ID_MAIN, TEXT_MAIN_CONTROL, 1, MENU_NODE_ATTR_OPERABLE | MENU_NODE_ATTR_VISIBLE},
|
||||
{MENU_NODE_ID_SETTING, MENU_NODE_ID_MAIN, TEXT_MAIN_SETTING, 1, MENU_NODE_ATTR_HAS_CHILDREN | MENU_NODE_ATTR_VISIBLE},
|
||||
{MENU_NODE_ID_RECORD, MENU_NODE_ID_MAIN, TEXT_MAIN_RECORD, 1, MENU_NODE_ATTR_HAS_CHILDREN | MENU_NODE_ATTR_VISIBLE},
|
||||
{MENU_NODE_ID_HELP, MENU_NODE_ID_MAIN, TEXT_MAIN_HELP, 1, MENU_NODE_ATTR_HAS_CHILDREN | MENU_NODE_ATTR_VISIBLE},
|
||||
{MENU_NODE_ID_ABOUT, MENU_NODE_ID_MAIN, TEXT_MAIN_ABOUT, 1, MENU_NODE_ATTR_HAS_CHILDREN | MENU_NODE_ATTR_VISIBLE},
|
||||
|
||||
// 二级菜单:测量菜单子项
|
||||
{MENU_NODE_ID_MEASUREMENT_VOLTAGE, MENU_NODE_ID_MEASUREMENT, TEXT_MEASURE_VOLTAGE, 2, MENU_NODE_ATTR_OPERABLE | MENU_NODE_ATTR_VISIBLE},
|
||||
{MENU_NODE_ID_MEASUREMENT_CURRENT, MENU_NODE_ID_MEASUREMENT, TEXT_MEASURE_CURRENT, 2, MENU_NODE_ATTR_OPERABLE | MENU_NODE_ATTR_VISIBLE},
|
||||
{MENU_NODE_ID_MEASUREMENT_POWER, MENU_NODE_ID_MEASUREMENT, TEXT_MEASURE_POWER, 2, MENU_NODE_ATTR_OPERABLE | MENU_NODE_ATTR_VISIBLE},
|
||||
{MENU_NODE_ID_MEASUREMENT_ENERGY, MENU_NODE_ID_MEASUREMENT, TEXT_MEASURE_ENERGY, 2, MENU_NODE_ATTR_OPERABLE | MENU_NODE_ATTR_VISIBLE},
|
||||
{MENU_NODE_ID_MEASUREMENT_FREQUENCY, MENU_NODE_ID_MEASUREMENT, TEXT_MEASURE_FREQUENCY, 2, MENU_NODE_ATTR_OPERABLE | MENU_NODE_ATTR_VISIBLE},
|
||||
{MENU_NODE_ID_MEASUREMENT_TEMPERATURE, MENU_NODE_ID_MEASUREMENT, TEXT_MEASURE_TEMPERATURE, 2, MENU_NODE_ATTR_OPERABLE | MENU_NODE_ATTR_VISIBLE},
|
||||
|
||||
// 二级菜单:保护菜单子项
|
||||
{MENU_NODE_ID_PROTECTION_OVER_VOLTAGE, MENU_NODE_ID_PROTECTION, TEXT_PROTECT_OVER_VOLTAGE, 2, MENU_NODE_ATTR_OPERABLE | MENU_NODE_ATTR_VISIBLE},
|
||||
{MENU_NODE_ID_PROTECTION_UNDER_VOLTAGE, MENU_NODE_ID_PROTECTION, TEXT_PROTECT_UNDER_VOLTAGE, 2, MENU_NODE_ATTR_OPERABLE | MENU_NODE_ATTR_VISIBLE},
|
||||
{MENU_NODE_ID_PROTECTION_OVER_CURRENT, MENU_NODE_ID_PROTECTION, TEXT_PROTECT_OVER_CURRENT, 2, MENU_NODE_ATTR_OPERABLE | MENU_NODE_ATTR_VISIBLE},
|
||||
{MENU_NODE_ID_PROTECTION_SETTING, MENU_NODE_ID_PROTECTION, TEXT_PROTECT_SETTING, 2, MENU_NODE_ATTR_OPERABLE | MENU_NODE_ATTR_VISIBLE},
|
||||
|
||||
// 二级菜单:控制菜单子项
|
||||
{MENU_NODE_ID_CONTROL_ON_OFF, MENU_NODE_ID_CONTROL, TEXT_CONTROL_ON_OFF, 2, MENU_NODE_ATTR_OPERABLE | MENU_NODE_ATTR_VISIBLE},
|
||||
{MENU_NODE_ID_CONTROL_START_STOP, MENU_NODE_ID_CONTROL, TEXT_CONTROL_START_STOP, 2, MENU_NODE_ATTR_OPERABLE | MENU_NODE_ATTR_VISIBLE},
|
||||
{MENU_NODE_ID_CONTROL_AUTO_MANUAL, MENU_NODE_ID_CONTROL, TEXT_CONTROL_AUTO_MANUAL, 2, MENU_NODE_ATTR_OPERABLE | MENU_NODE_ATTR_VISIBLE},
|
||||
|
||||
// 二级菜单:其他子项
|
||||
{MENU_NODE_ID_SETTING_LANGUAGE, MENU_NODE_ID_SETTING, TEXT_SETTING_LANGUAGE, 2, MENU_NODE_ATTR_OPERABLE | MENU_NODE_ATTR_VISIBLE},
|
||||
{MENU_NODE_ID_RECORD_EVENT, MENU_NODE_ID_RECORD, TEXT_RECORD_EVENT, 2, MENU_NODE_ATTR_OPERABLE | MENU_NODE_ATTR_VISIBLE},
|
||||
{MENU_NODE_ID_HELP_GUIDE, MENU_NODE_ID_HELP, TEXT_HELP_GUIDE, 2, MENU_NODE_ATTR_OPERABLE | MENU_NODE_ATTR_VISIBLE},
|
||||
{MENU_NODE_ID_ABOUT_VERSION, MENU_NODE_ID_ABOUT, TEXT_ABOUT_VERSION, 2, MENU_NODE_ATTR_OPERABLE | MENU_NODE_ATTR_VISIBLE},
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 菜单节点映射表大小
|
||||
*/
|
||||
const uint32_t menu_node_map_size = sizeof(menu_node_map) / sizeof(menu_node_map[0]);
|
||||
|
||||
/* 函数实现 ---------------------------------------------------------------------------------------------------------*/
|
||||
|
||||
/**
|
||||
* @brief 初始化语言模块
|
||||
*/
|
||||
MenuErrCode language_init(void)
|
||||
{
|
||||
// 初始化核心菜单库语言模块
|
||||
MenuErrCode ret = menu_lang_init();
|
||||
if (ret != MENU_ERR_OK)
|
||||
{
|
||||
return ret;
|
||||
}
|
||||
|
||||
// 设置默认语言
|
||||
return set_language(SYSTEM_LANG_CHINESE);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 根据文本ID获取对应语言的文本字符串
|
||||
*/
|
||||
const char* get_text(TextId_e text_id)
|
||||
{
|
||||
if (text_id >= TEXT_MAX)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (sg_current_language >= SYSTEM_LANG_MAX)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return sg_language_text_map[sg_current_language][text_id];
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 设置当前系统语言
|
||||
*/
|
||||
MenuErrCode set_language(SystemLang_e lang)
|
||||
{
|
||||
if (lang >= SYSTEM_LANG_MAX)
|
||||
{
|
||||
return MENU_ERR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
sg_current_language = lang;
|
||||
|
||||
// 同步到核心菜单库
|
||||
return menu_lang_set_current((LangId)lang);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取当前系统语言
|
||||
*/
|
||||
SystemLang_e get_current_language(void)
|
||||
{
|
||||
return sg_current_language;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 根据菜单节点ID获取对应的文本ID
|
||||
*/
|
||||
TextId_e get_menu_text_id(MenuNodeId_e node_id)
|
||||
{
|
||||
for (uint32_t i = 0; i < menu_node_map_size; i++)
|
||||
{
|
||||
if (menu_node_map[i].node_id == node_id)
|
||||
{
|
||||
return menu_node_map[i].text_id;
|
||||
}
|
||||
}
|
||||
|
||||
return TEXT_MAX;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 根据菜单节点ID获取对应的父节点ID
|
||||
*/
|
||||
MenuNodeId_e get_menu_parent_id(MenuNodeId_e node_id)
|
||||
{
|
||||
for (uint32_t i = 0; i < menu_node_map_size; i++)
|
||||
{
|
||||
if (menu_node_map[i].node_id == node_id)
|
||||
{
|
||||
return menu_node_map[i].parent_id;
|
||||
}
|
||||
}
|
||||
|
||||
return MENU_NODE_ID_NONE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 检查菜单节点是否有子节点
|
||||
*/
|
||||
uint8_t menu_node_has_children(MenuNodeId_e node_id)
|
||||
{
|
||||
for (uint32_t i = 0; i < menu_node_map_size; i++)
|
||||
{
|
||||
if (menu_node_map[i].node_id == node_id)
|
||||
{
|
||||
return MENU_NODE_HAS_ATTR(menu_node_map[i].attributes, MENU_NODE_ATTR_HAS_CHILDREN);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 检查菜单节点是否可操作
|
||||
*/
|
||||
uint8_t menu_node_is_operable(MenuNodeId_e node_id)
|
||||
{
|
||||
for (uint32_t i = 0; i < menu_node_map_size; i++)
|
||||
{
|
||||
if (menu_node_map[i].node_id == node_id)
|
||||
{
|
||||
return MENU_NODE_HAS_ATTR(menu_node_map[i].attributes, MENU_NODE_ATTR_OPERABLE);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取菜单节点的显示文本
|
||||
*/
|
||||
const char* get_menu_node_text(MenuNodeId_e node_id)
|
||||
{
|
||||
TextId_e text_id = get_menu_text_id(node_id);
|
||||
if (text_id == TEXT_MAX)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return get_text(text_id);
|
||||
}
|
||||
311
demo/language.h
311
demo/language.h
@ -1,311 +0,0 @@
|
||||
#ifndef LANGUAGE_H
|
||||
#define LANGUAGE_H
|
||||
|
||||
/* Includes ----------------------------------------------------------------------------------------------------------*/
|
||||
#include "../../src/lang/menu_lang.h"
|
||||
#include "../../src/core/menu_core.h"
|
||||
|
||||
/* 类型定义 ---------------------------------------------------------------------------------------------------------*/
|
||||
|
||||
/**
|
||||
* @brief 系统语言枚举
|
||||
* @note 与核心菜单库语言ID映射,便于扩展
|
||||
*/
|
||||
typedef enum
|
||||
{
|
||||
SYSTEM_LANG_CHINESE = 0, // 中文(默认)
|
||||
SYSTEM_LANG_ENGLISH, // 英文
|
||||
SYSTEM_LANG_JAPANESE, // 日语
|
||||
SYSTEM_LANG_GERMAN, // 德语
|
||||
SYSTEM_LANG_MAX, // 语言类型总数(仅用于遍历/边界判断)
|
||||
} SystemLang_e;
|
||||
|
||||
/**
|
||||
* @brief 文本ID枚举(按模块分级)
|
||||
* @note 采用模块化命名,便于扩展和维护
|
||||
* 分级规则:
|
||||
* - 基础文本(0x0000~0x00FF)
|
||||
* - 主菜单(0x0100~0x01FF)
|
||||
* - 测量菜单(0x0200~0x02FF)
|
||||
* - 保护菜单(0x0300~0x03FF)
|
||||
* - 控制菜单(0x0400~0x04FF)
|
||||
* - 设置菜单(0x0500~0x05FF)
|
||||
* - 记录菜单(0x0600~0x06FF)
|
||||
* - 帮助菜单(0x0700~0x07FF)
|
||||
* - 关于菜单(0x0800~0x08FF)
|
||||
* - 持久化相关(0x0900~0x09FF)
|
||||
*/
|
||||
typedef enum
|
||||
{
|
||||
// 基础文本(0x0000 ~ 0x00FF)
|
||||
TEXT_ENTER = 0x0000, // 进入
|
||||
TEXT_EXIT, // 退出
|
||||
TEXT_RETURN, // 返回
|
||||
TEXT_OK, // 确定
|
||||
TEXT_CANCEL, // 取消
|
||||
TEXT_SAVE, // 保存
|
||||
TEXT_LOAD, // 加载
|
||||
|
||||
// 主菜单文本(0x0100 ~ 0x01FF)
|
||||
TEXT_MAIN_MENU = 0x0100, // 主菜单
|
||||
TEXT_MAIN_MEASUREMENT, // 测量
|
||||
TEXT_MAIN_PROTECTION, // 保护
|
||||
TEXT_MAIN_CONTROL, // 控制
|
||||
TEXT_MAIN_SETTING, // 设置
|
||||
TEXT_MAIN_RECORD, // 记录
|
||||
TEXT_MAIN_HELP, // 帮助
|
||||
TEXT_MAIN_ABOUT, // 关于
|
||||
|
||||
// 测量菜单文本(0x0200 ~ 0x02FF)
|
||||
TEXT_MEASURE_VOLTAGE = 0x0200, // 电压
|
||||
TEXT_MEASURE_CURRENT, // 电流
|
||||
TEXT_MEASURE_POWER, // 功率
|
||||
TEXT_MEASURE_ENERGY, // 能量
|
||||
TEXT_MEASURE_FREQUENCY, // 频率
|
||||
TEXT_MEASURE_TEMPERATURE, // 温度
|
||||
|
||||
// 保护菜单文本(0x0300 ~ 0x03FF)
|
||||
TEXT_PROTECT_OVER_VOLTAGE = 0x0300, // 过电压保护
|
||||
TEXT_PROTECT_UNDER_VOLTAGE, // 欠电压保护
|
||||
TEXT_PROTECT_OVER_CURRENT, // 过电流保护
|
||||
TEXT_PROTECT_SETTING, // 保护设置
|
||||
|
||||
// 控制菜单文本(0x0400 ~ 0x04FF)
|
||||
TEXT_CONTROL_ON_OFF = 0x0400, // 开关控制
|
||||
TEXT_CONTROL_START_STOP, // 启停控制
|
||||
TEXT_CONTROL_AUTO_MANUAL, // 自动/手动切换
|
||||
|
||||
// 设置菜单文本(0x0500 ~ 0x05FF)
|
||||
TEXT_SETTING_LANGUAGE = 0x0500, // 语言设置
|
||||
|
||||
// 记录菜单文本(0x0600 ~ 0x06FF)
|
||||
TEXT_RECORD_EVENT = 0x0600, // 事件记录
|
||||
|
||||
// 帮助菜单文本(0x0700 ~ 0x07FF)
|
||||
TEXT_HELP_GUIDE = 0x0700, // 指南
|
||||
|
||||
// 关于菜单文本(0x0800 ~ 0x08FF)
|
||||
TEXT_ABOUT_VERSION = 0x0800, // 版本信息
|
||||
|
||||
// 持久化相关文本(0x0900 ~ 0x09FF)
|
||||
TEXT_PERSIST_SAVE_STATE = 0x0900, // 保存菜单状态
|
||||
TEXT_PERSIST_RESTORE_STATE, // 恢复菜单状态
|
||||
TEXT_PERSIST_SAVE_SUCCESS, // 保存成功
|
||||
TEXT_PERSIST_SAVE_FAILED, // 保存失败
|
||||
TEXT_PERSIST_RESTORE_SUCCESS, // 恢复成功
|
||||
TEXT_PERSIST_RESTORE_FAILED, // 恢复失败
|
||||
|
||||
// 提示信息(0x0A00 ~ 0x0AFF)
|
||||
TEXT_ERROR = 0x0A00, // 错误
|
||||
TEXT_WARNING, // 警告
|
||||
TEXT_SUCCESS, // 成功
|
||||
TEXT_INFO, // 信息
|
||||
TEXT_TEST, // 测试
|
||||
TEXT_SELECT_OPTION, // 选择操作
|
||||
TEXT_CURRENT, // 当前
|
||||
TEXT_STATUS, // 状态
|
||||
TEXT_SUMMARY, // 摘要
|
||||
TEXT_EMPTY, // 空
|
||||
TEXT_CURRENT_SELECTION, // 当前选择
|
||||
TEXT_OPERATIONS, // 操作
|
||||
TEXT_SELECT_ITEM, // 选择项
|
||||
TEXT_ENTER_SUBMENU, // 进入子菜单
|
||||
TEXT_RETURN_PARENT, // 返回父菜单
|
||||
TEXT_SELECT_PREVIOUS, // 选择上一项
|
||||
TEXT_SELECT_NEXT, // 选择下一项
|
||||
TEXT_START_DEFAULT, // 从默认开始
|
||||
TEXT_REGISTER_CALLBACK_FAILED, // 注册回调失败
|
||||
TEXT_RESTORE_MENU_STATE, // 恢复菜单状态
|
||||
TEXT_RESTORE_MENU_STATE_FAILED, // 恢复菜单状态失败
|
||||
TEXT_SAVE_MENU_STATE_FAILED, // 保存菜单状态失败
|
||||
TEXT_SAVE_MENU_STATE, // 保存菜单状态
|
||||
|
||||
// 操作提示(0x0B00 ~ 0x0BFF)
|
||||
TEXT_UP_ARROW = 0x0B00, // 上箭头
|
||||
TEXT_DOWN_ARROW, // 下箭头
|
||||
TEXT_LEFT_ARROW, // 左箭头
|
||||
TEXT_RIGHT_ARROW, // 右箭头
|
||||
|
||||
TEXT_MAX = 0x0FFF, // 所有文本ID最大值(用于边界检查)
|
||||
} TextId_e;
|
||||
|
||||
/**
|
||||
* @brief 菜单节点ID枚举
|
||||
* @note 采用模块化命名,便于扩展和维护
|
||||
* 分级规则:
|
||||
* - 一级菜单(0x1000~0x1FFF)
|
||||
* - 二级菜单(0x2000~0x2FFF)
|
||||
* - 三级菜单(0x3000~0x3FFF)
|
||||
* - 四级菜单(0x4000~0x4FFF)
|
||||
*/
|
||||
typedef enum
|
||||
{
|
||||
// 无效节点/根节点标记
|
||||
MENU_NODE_ID_NONE = 0x0000,
|
||||
|
||||
// 一级菜单(0x1000 ~ 0x1FFF)
|
||||
MENU_NODE_ID_MAIN = 0x1000, // 主菜单
|
||||
MENU_NODE_ID_MEASUREMENT, // 测量菜单
|
||||
MENU_NODE_ID_PROTECTION, // 保护菜单
|
||||
MENU_NODE_ID_CONTROL, // 控制菜单
|
||||
MENU_NODE_ID_SETTING, // 设置菜单
|
||||
MENU_NODE_ID_RECORD, // 记录菜单
|
||||
MENU_NODE_ID_HELP, // 帮助菜单
|
||||
MENU_NODE_ID_ABOUT, // 关于菜单
|
||||
|
||||
// 二级菜单:测量菜单子项(0x2000 ~ 0x20FF)
|
||||
MENU_NODE_ID_MEASUREMENT_VOLTAGE = 0x2000, // 电压测量
|
||||
MENU_NODE_ID_MEASUREMENT_CURRENT, // 电流测量
|
||||
MENU_NODE_ID_MEASUREMENT_POWER, // 功率测量
|
||||
MENU_NODE_ID_MEASUREMENT_ENERGY, // 能量测量
|
||||
MENU_NODE_ID_MEASUREMENT_FREQUENCY, // 频率测量
|
||||
MENU_NODE_ID_MEASUREMENT_TEMPERATURE, // 温度测量
|
||||
|
||||
// 二级菜单:保护菜单子项(0x2100 ~ 0x21FF)
|
||||
MENU_NODE_ID_PROTECTION_OVER_VOLTAGE = 0x2100, // 过电压保护
|
||||
MENU_NODE_ID_PROTECTION_UNDER_VOLTAGE, // 欠电压保护
|
||||
MENU_NODE_ID_PROTECTION_OVER_CURRENT, // 过电流保护
|
||||
MENU_NODE_ID_PROTECTION_SETTING, // 保护设置
|
||||
|
||||
// 二级菜单:控制菜单子项(0x2200 ~ 0x22FF)
|
||||
MENU_NODE_ID_CONTROL_ON_OFF = 0x2200, // 开关控制
|
||||
MENU_NODE_ID_CONTROL_START_STOP, // 启停控制
|
||||
MENU_NODE_ID_CONTROL_AUTO_MANUAL, // 自动/手动切换
|
||||
|
||||
// 二级菜单:设置菜单子项(0x2300 ~ 0x23FF)
|
||||
MENU_NODE_ID_SETTING_LANGUAGE = 0x2300, // 语言设置
|
||||
|
||||
// 二级菜单:记录菜单子项(0x2400 ~ 0x24FF)
|
||||
MENU_NODE_ID_RECORD_EVENT = 0x2400, // 事件记录
|
||||
|
||||
// 二级菜单:帮助菜单子项(0x2500 ~ 0x25FF)
|
||||
MENU_NODE_ID_HELP_GUIDE = 0x2500, // 指南
|
||||
|
||||
// 二级菜单:关于菜单子项(0x2600 ~ 0x26FF)
|
||||
MENU_NODE_ID_ABOUT_VERSION = 0x2600, // 版本信息
|
||||
|
||||
MENU_NODE_ID_MAX = 0x4FFF, // 菜单节点ID最大值
|
||||
} MenuNodeId_e;
|
||||
|
||||
/**
|
||||
* @brief 菜单节点属性枚举
|
||||
* @note 使用位掩码方式,便于组合和扩展
|
||||
*/
|
||||
typedef enum
|
||||
{
|
||||
MENU_NODE_ATTR_NONE = 0x00, // 无属性
|
||||
MENU_NODE_ATTR_HAS_CHILDREN = 0x01, // 有子节点
|
||||
MENU_NODE_ATTR_OPERABLE = 0x02, // 可操作
|
||||
MENU_NODE_ATTR_VISIBLE = 0x04, // 可见
|
||||
MENU_NODE_ATTR_EDITABLE = 0x08, // 可编辑
|
||||
} MenuNodeAttr_e;
|
||||
|
||||
/**
|
||||
* @brief 菜单节点信息结构体
|
||||
* @note 用于建立菜单节点ID与文本ID、父子关系、属性等映射
|
||||
* 采用更紧凑的设计,便于扩展
|
||||
*/
|
||||
typedef struct
|
||||
{
|
||||
MenuNodeId_e node_id; // 当前菜单节点ID(唯一标识)
|
||||
MenuNodeId_e parent_id; // 父节点ID(MENU_NODE_ID_NONE表示无父节点)
|
||||
TextId_e text_id; // 对应显示的文本ID
|
||||
uint8_t level; // 菜单层级(1=一级,2=二级,3=三级,4=四级)
|
||||
uint8_t attributes; // 节点属性(使用MenuNodeAttr_e位掩码组合)
|
||||
} MenuNodeInfo_t;
|
||||
|
||||
/* 宏定义 ---------------------------------------------------------------------------------------------------------*/
|
||||
|
||||
/**
|
||||
* @brief 菜单节点属性检查宏
|
||||
* @param attr 节点属性
|
||||
* @param flag 要检查的属性标志
|
||||
* @return 1表示属性存在,0表示属性不存在
|
||||
*/
|
||||
#define MENU_NODE_HAS_ATTR(attr, flag) ((attr) & (flag))
|
||||
|
||||
/**
|
||||
* @brief 菜单框架配置常量
|
||||
*/
|
||||
#define MENU_CFG_MAX_DEPTH 4 // 最大菜单深度(一级~四级)
|
||||
#define MENU_CFG_MAX_NODES 50 // 最大菜单节点数
|
||||
#define MENU_CFG_MAX_TEXT_LEN 50 // 最大文本长度(字符数)
|
||||
|
||||
/* 全局变量声明 ---------------------------------------------------------------------------------------------------*/
|
||||
|
||||
/**
|
||||
* @brief 全局菜单节点映射表
|
||||
* @note 所有菜单节点的关联关系集中在这里维护,修改时仅需调整此表
|
||||
* 按一级菜单、二级菜单的顺序排列,便于阅读和维护
|
||||
*/
|
||||
extern const MenuNodeInfo_t menu_node_map[];
|
||||
|
||||
/**
|
||||
* @brief 菜单节点映射表大小
|
||||
*/
|
||||
extern const uint32_t menu_node_map_size;
|
||||
|
||||
/* 函数声明 ---------------------------------------------------------------------------------------------------------*/
|
||||
|
||||
/**
|
||||
* @brief 初始化语言模块
|
||||
* @return MenuErrCode 操作结果
|
||||
*/
|
||||
MenuErrCode language_init(void);
|
||||
|
||||
/**
|
||||
* @brief 根据文本ID获取对应语言的文本字符串
|
||||
* @param text_id 文本ID
|
||||
* @return const char* 对应语言的文本字符串,若ID无效返回NULL
|
||||
*/
|
||||
const char* get_text(TextId_e text_id);
|
||||
|
||||
/**
|
||||
* @brief 设置当前系统语言
|
||||
* @param lang 要设置的语言
|
||||
* @return MenuErrCode 操作结果
|
||||
*/
|
||||
MenuErrCode set_language(SystemLang_e lang);
|
||||
|
||||
/**
|
||||
* @brief 获取当前系统语言
|
||||
* @return SystemLang_e 当前使用的语言
|
||||
*/
|
||||
SystemLang_e get_current_language(void);
|
||||
|
||||
/**
|
||||
* @brief 根据菜单节点ID获取对应的文本ID
|
||||
* @param node_id 菜单节点ID
|
||||
* @return TextId_e 对应的文本ID,若节点不存在返回TEXT_MAX
|
||||
*/
|
||||
TextId_e get_menu_text_id(MenuNodeId_e node_id);
|
||||
|
||||
/**
|
||||
* @brief 根据菜单节点ID获取对应的父节点ID
|
||||
* @param node_id 菜单节点ID
|
||||
* @return MenuNodeId_e 对应的父节点ID,若节点不存在返回MENU_NODE_ID_NONE
|
||||
*/
|
||||
MenuNodeId_e get_menu_parent_id(MenuNodeId_e node_id);
|
||||
|
||||
/**
|
||||
* @brief 检查菜单节点是否有子节点
|
||||
* @param node_id 菜单节点ID
|
||||
* @return uint8_t 1=有子节点,0=无子节点/节点不存在
|
||||
*/
|
||||
uint8_t menu_node_has_children(MenuNodeId_e node_id);
|
||||
|
||||
/**
|
||||
* @brief 检查菜单节点是否可操作
|
||||
* @param node_id 菜单节点ID
|
||||
* @return uint8_t 1=可操作,0=不可操作/节点不存在
|
||||
*/
|
||||
uint8_t menu_node_is_operable(MenuNodeId_e node_id);
|
||||
|
||||
/**
|
||||
* @brief 获取菜单节点的显示文本
|
||||
* @param node_id 菜单节点ID
|
||||
* @return const char* 对应语言的文本字符串,若节点无效返回NULL
|
||||
*/
|
||||
const char* get_menu_node_text(MenuNodeId_e node_id);
|
||||
|
||||
#endif /* LANGUAGE_H */
|
||||
@ -1,83 +0,0 @@
|
||||
# This is the CMakeCache file.
|
||||
# For build in directory: e:/Jfen_work/local_git_code/menu/examples/build
|
||||
# It was generated by CMake: C:/Users/ZHIZHANKEJI/AppData/Local/Programs/Python/Python311/Lib/site-packages/cmake/data/bin/cmake.exe
|
||||
# You can edit this file to change values found and used by cmake.
|
||||
# If you do not want to change any of the values, simply exit the editor.
|
||||
# If you do want to change a value, simply edit, save, and exit the editor.
|
||||
# The syntax for the file is as follows:
|
||||
# KEY:TYPE=VALUE
|
||||
# KEY is the name of a variable in the cache.
|
||||
# TYPE is a hint to GUIs for the type of VALUE, DO NOT EDIT TYPE!.
|
||||
# VALUE is the current value for the KEY.
|
||||
|
||||
########################
|
||||
# EXTERNAL cache entries
|
||||
########################
|
||||
|
||||
//Value Computed by CMake.
|
||||
CMAKE_FIND_PACKAGE_REDIRECTS_DIR:STATIC=E:/Jfen_work/local_git_code/menu/examples/build/CMakeFiles/pkgRedirects
|
||||
|
||||
//Program used to build from makefiles.
|
||||
CMAKE_MAKE_PROGRAM:STRING=nmake
|
||||
|
||||
//Value Computed by CMake
|
||||
CMAKE_PROJECT_DESCRIPTION:STATIC=
|
||||
|
||||
//Value Computed by CMake
|
||||
CMAKE_PROJECT_HOMEPAGE_URL:STATIC=
|
||||
|
||||
//Value Computed by CMake
|
||||
CMAKE_PROJECT_NAME:STATIC=Project
|
||||
|
||||
//Value Computed by CMake
|
||||
Project_BINARY_DIR:STATIC=E:/Jfen_work/local_git_code/menu/examples/build
|
||||
|
||||
//Value Computed by CMake
|
||||
Project_IS_TOP_LEVEL:STATIC=ON
|
||||
|
||||
//Value Computed by CMake
|
||||
Project_SOURCE_DIR:STATIC=E:/Jfen_work/local_git_code/menu/examples
|
||||
|
||||
|
||||
########################
|
||||
# INTERNAL cache entries
|
||||
########################
|
||||
|
||||
//This is the directory where this CMakeCache.txt was created
|
||||
CMAKE_CACHEFILE_DIR:INTERNAL=e:/Jfen_work/local_git_code/menu/examples/build
|
||||
//Major version of cmake used to create the current loaded cache
|
||||
CMAKE_CACHE_MAJOR_VERSION:INTERNAL=3
|
||||
//Minor version of cmake used to create the current loaded cache
|
||||
CMAKE_CACHE_MINOR_VERSION:INTERNAL=31
|
||||
//Patch version of cmake used to create the current loaded cache
|
||||
CMAKE_CACHE_PATCH_VERSION:INTERNAL=2
|
||||
//Path to CMake executable.
|
||||
CMAKE_COMMAND:INTERNAL=C:/Users/ZHIZHANKEJI/AppData/Local/Programs/Python/Python311/Lib/site-packages/cmake/data/bin/cmake.exe
|
||||
//Path to cpack program executable.
|
||||
CMAKE_CPACK_COMMAND:INTERNAL=C:/Users/ZHIZHANKEJI/AppData/Local/Programs/Python/Python311/Lib/site-packages/cmake/data/bin/cpack.exe
|
||||
//Path to ctest program executable.
|
||||
CMAKE_CTEST_COMMAND:INTERNAL=C:/Users/ZHIZHANKEJI/AppData/Local/Programs/Python/Python311/Lib/site-packages/cmake/data/bin/ctest.exe
|
||||
//Path to cache edit program executable.
|
||||
CMAKE_EDIT_COMMAND:INTERNAL=C:/Users/ZHIZHANKEJI/AppData/Local/Programs/Python/Python311/Lib/site-packages/cmake/data/bin/cmake-gui.exe
|
||||
//Name of external makefile project generator.
|
||||
CMAKE_EXTRA_GENERATOR:INTERNAL=
|
||||
//Name of generator.
|
||||
CMAKE_GENERATOR:INTERNAL=NMake Makefiles
|
||||
//Generator instance identifier.
|
||||
CMAKE_GENERATOR_INSTANCE:INTERNAL=
|
||||
//Name of generator platform.
|
||||
CMAKE_GENERATOR_PLATFORM:INTERNAL=
|
||||
//Name of generator toolset.
|
||||
CMAKE_GENERATOR_TOOLSET:INTERNAL=
|
||||
//Source directory with the top level CMakeLists.txt file for this
|
||||
// project
|
||||
CMAKE_HOME_DIRECTORY:INTERNAL=E:/Jfen_work/local_git_code/menu/examples
|
||||
//ADVANCED property for variable: CMAKE_MAKE_PROGRAM
|
||||
CMAKE_MAKE_PROGRAM-ADVANCED:INTERNAL=1
|
||||
//number of local generators
|
||||
CMAKE_NUMBER_OF_MAKEFILES:INTERNAL=1
|
||||
//Platform information initialized
|
||||
CMAKE_PLATFORM_INFO_INITIALIZED:INTERNAL=1
|
||||
//Path to CMake installation.
|
||||
CMAKE_ROOT:INTERNAL=C:/Users/ZHIZHANKEJI/AppData/Local/Programs/Python/Python311/Lib/site-packages/cmake/data/share/cmake-3.31
|
||||
|
||||
@ -1,15 +0,0 @@
|
||||
set(CMAKE_HOST_SYSTEM "Windows-10.0.26200")
|
||||
set(CMAKE_HOST_SYSTEM_NAME "Windows")
|
||||
set(CMAKE_HOST_SYSTEM_VERSION "10.0.26200")
|
||||
set(CMAKE_HOST_SYSTEM_PROCESSOR "AMD64")
|
||||
|
||||
|
||||
|
||||
set(CMAKE_SYSTEM "Windows-10.0.26200")
|
||||
set(CMAKE_SYSTEM_NAME "Windows")
|
||||
set(CMAKE_SYSTEM_VERSION "10.0.26200")
|
||||
set(CMAKE_SYSTEM_PROCESSOR "AMD64")
|
||||
|
||||
set(CMAKE_CROSSCOMPILING "FALSE")
|
||||
|
||||
set(CMAKE_SYSTEM_LOADED 1)
|
||||
@ -1,11 +0,0 @@
|
||||
|
||||
---
|
||||
events:
|
||||
-
|
||||
kind: "message-v1"
|
||||
backtrace:
|
||||
- "C:/Users/ZHIZHANKEJI/AppData/Local/Programs/Python/Python311/Lib/site-packages/cmake/data/share/cmake-3.31/Modules/CMakeDetermineSystem.cmake:205 (message)"
|
||||
- "CMakeLists.txt"
|
||||
message: |
|
||||
The system is: Windows - 10.0.26200 - AMD64
|
||||
...
|
||||
@ -1 +0,0 @@
|
||||
# This file is generated by cmake for dependency checking of the CMakeCache.txt file
|
||||
@ -1,13 +0,0 @@
|
||||
#ifndef HMI_CAMERA_H
|
||||
#define HMI_CAMERA_H
|
||||
|
||||
#include "hmi_common.h"
|
||||
|
||||
extern const MenuImage_t sgc_CameraImage;
|
||||
|
||||
void Hmi_CameraEnter(const MenuItemInfo_t *pItemInfo);
|
||||
void Hmi_CameraExit(const MenuItemInfo_t *pItemInfo);
|
||||
void Hmi_CameraLoad(const MenuItemInfo_t *pItemInfo);
|
||||
void Hmi_CameraTask(const MenuItemInfo_t *pItemInfo);
|
||||
|
||||
#endif
|
||||
@ -1,20 +0,0 @@
|
||||
#ifndef HMI_COMMON_H
|
||||
#define HMI_COMMON_H
|
||||
|
||||
#include "menu.h"
|
||||
|
||||
/* 自定义图标数据 */
|
||||
typedef struct
|
||||
{
|
||||
const char *pImageFrame;
|
||||
const char *pImage;
|
||||
} MenuImage_t;
|
||||
|
||||
// 清除屏幕
|
||||
#define CLEAR() printf("\033[2J")
|
||||
// 定位光标
|
||||
#define MOVETO(x,y) printf("\033[%d;%dH", (x), (y))
|
||||
|
||||
void Hmi_OnCommonFunction(const MenuItemInfo_t *pItemInfo);
|
||||
|
||||
#endif
|
||||
@ -1,12 +0,0 @@
|
||||
#ifndef HMI_MORE_SET_H
|
||||
#define HMI_MORE_SET_H
|
||||
|
||||
#include "hmi_common.h"
|
||||
|
||||
|
||||
void Hmi_MoreSetEnter(const MenuItemInfo_t *pItemInfo);
|
||||
void Hmi_MoreSetExit(const MenuItemInfo_t *pItemInfo);
|
||||
void Hmi_MoreSetLoad(const MenuItemInfo_t *pItemInfo);
|
||||
void Hmi_MoreSetTask(const MenuItemInfo_t *pItemInfo);
|
||||
|
||||
#endif
|
||||
@ -1,14 +0,0 @@
|
||||
#ifndef HMI_MUSIC_H
|
||||
#define HMI_MUSIC_H
|
||||
|
||||
#include "hmi_common.h"
|
||||
|
||||
|
||||
extern const MenuImage_t sgc_MusicImage;
|
||||
|
||||
void Hmi_MusicEnter(const MenuItemInfo_t *pItemInfo);
|
||||
void Hmi_MusicExit(const MenuItemInfo_t *pItemInfo);
|
||||
void Hmi_MusicLoad(const MenuItemInfo_t *pItemInfo);
|
||||
void Hmi_MusicTask(const MenuItemInfo_t *pItemInfo);
|
||||
|
||||
#endif
|
||||
@ -1,13 +0,0 @@
|
||||
#ifndef HMI_SET_H
|
||||
#define HMI_SET_H
|
||||
|
||||
#include "hmi_common.h"
|
||||
|
||||
extern const MenuImage_t sgc_SettingImage;
|
||||
|
||||
void Hmi_SetEnter(const MenuItemInfo_t *pItemInfo);
|
||||
void Hmi_SetExit(const MenuItemInfo_t *pItemInfo);
|
||||
void Hmi_SetLoad(const MenuItemInfo_t *pItemInfo);
|
||||
void Hmi_SetTask(const MenuItemInfo_t *pItemInfo);
|
||||
|
||||
#endif
|
||||
@ -1,12 +0,0 @@
|
||||
#ifndef HMI_VIDEO_H
|
||||
#define HMI_VIDEO_H
|
||||
|
||||
#include "hmi_common.h"
|
||||
|
||||
extern const MenuImage_t sgc_VideoImage;
|
||||
|
||||
void Hmi_VideoLoad(const MenuItemInfo_t *pItemInfo);
|
||||
void Hmi_VideoExit(const MenuItemInfo_t *pItemInfo);
|
||||
void Hmi_VideoTask(const MenuItemInfo_t *pItemInfo);
|
||||
|
||||
#endif
|
||||
@ -1,9 +0,0 @@
|
||||
#ifndef MAIN_HMI_H
|
||||
#define MAIN_HMI_H
|
||||
|
||||
#include "hmi_common.h"
|
||||
|
||||
void Hmi_LoadMainHmi(const MenuItemInfo_t *pItemInfo);
|
||||
void Hmi_MainTask(const MenuItemInfo_t *pItemInfo);
|
||||
|
||||
#endif
|
||||
@ -1,103 +0,0 @@
|
||||
#include "hmi_camera.h"
|
||||
#include "menu.h"
|
||||
#include "language.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
const MenuImage_t sgc_CameraImage = {
|
||||
"**********",
|
||||
"&"
|
||||
};
|
||||
|
||||
|
||||
MenuList_t sg_CameraMenuTable[] =
|
||||
{
|
||||
MENU_ITEM_BIND("TEXT_PHOTO", NULL, NULL, NULL, Hmi_OnCommonFunction, NULL),
|
||||
MENU_ITEM_BIND(TEXT_PHOTOGRAPHY, NULL, NULL, NULL, Hmi_OnCommonFunction, NULL),
|
||||
};
|
||||
|
||||
static void ShowCameraMenu(MenuShow_t *ptShowInfo)
|
||||
{
|
||||
uint8_t showNum = 3;
|
||||
menusize_t tmpselect;
|
||||
|
||||
Menu_LimitShowListNum(ptShowInfo, &showNum);
|
||||
|
||||
printf("\e[0;30;46m ------------- %s ------------- \e[0m\n", get_text(ptShowInfo->uMenuDesc.textId));
|
||||
|
||||
for (int i = 0; i < showNum; i++)
|
||||
{
|
||||
tmpselect = i + ptShowInfo->showBaseItem;
|
||||
|
||||
if (tmpselect == ptShowInfo->selectItem)
|
||||
{
|
||||
printf("\e[0;30;47m %d. %-34s\e[0m\n", tmpselect + 1, get_text(ptShowInfo->uItemsListDesc[tmpselect].textId));
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("\e[7;30;47m %d. %-34s\e[0m\n", tmpselect + 1, get_text(ptShowInfo->uItemsListDesc[tmpselect].textId));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Hmi_CameraEnter(const MenuItemInfo_t *pItemInfo)
|
||||
{
|
||||
Menu_Bind(sg_CameraMenuTable, MENU_GET_NUM(sg_CameraMenuTable), ShowCameraMenu);
|
||||
}
|
||||
|
||||
void Hmi_CameraLoad(const MenuItemInfo_t *pItemInfo)
|
||||
{
|
||||
CLEAR();
|
||||
MOVETO(0, 0);
|
||||
printf("---%s-----\n", get_text(TEXT_LOADING));
|
||||
}
|
||||
|
||||
void Hmi_CameraExit(const MenuItemInfo_t *pItemInfo)
|
||||
{
|
||||
printf("--------------------------\n");
|
||||
printf(" %s%s...\n", get_text(TEXT_VIDEO), get_text(TEXT_STOPPING_PLAYBACK));
|
||||
printf("--------------------------\n");
|
||||
#ifdef _WIN32
|
||||
Sleep(1000);
|
||||
#else
|
||||
sleep(1);
|
||||
#endif
|
||||
}
|
||||
|
||||
void Hmi_CameraTask(const MenuItemInfo_t *pItemInfo)
|
||||
{
|
||||
int cmd;
|
||||
|
||||
printf("%s(0-%s; 1-%s%s; 2-%s; 3-%s; 4-%s): ",
|
||||
get_text(TEXT_SELECT_OPTION), get_text(TEXT_RETURN), get_text(TEXT_RETURN), get_text(TEXT_MAIN_MENU),
|
||||
get_text(TEXT_ENTER), get_text(TEXT_NEXT), get_text(TEXT_PREVIOUS));
|
||||
scanf(" %d", &cmd);
|
||||
|
||||
switch (cmd)
|
||||
{
|
||||
case 0:
|
||||
Menu_Exit(true);
|
||||
break;
|
||||
case 1:
|
||||
Menu_Reset();
|
||||
break;
|
||||
case 2:
|
||||
Menu_Enter();
|
||||
break;
|
||||
case 3:
|
||||
Menu_SelectNext(true);
|
||||
break;
|
||||
case 4:
|
||||
Menu_SelectPrevious(true);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -1,28 +0,0 @@
|
||||
#include "hmi_common.h"
|
||||
#include "language.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
void Hmi_OnCommonFunction(const MenuItemInfo_t *pItemInfo)
|
||||
{
|
||||
int cmd;
|
||||
|
||||
printf("--------------------------\n");
|
||||
printf(" %s%s\n", get_text(pItemInfo->uMenuDesc.textId), get_text(TEXT_FUNCTION_TEST));
|
||||
printf("--------------------------\n");
|
||||
|
||||
printf("%s(0-%s): ",
|
||||
get_text(TEXT_SELECT_OPTION), get_text(TEXT_EXIT));
|
||||
scanf(" %d", &cmd); // 空格作用是忽略上次的回车
|
||||
|
||||
if (cmd == 0)
|
||||
{
|
||||
Menu_Exit(false);
|
||||
}
|
||||
}
|
||||
@ -1,110 +0,0 @@
|
||||
#include "hmi_more_set.h"
|
||||
#include "menu.h"
|
||||
#include "language.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
|
||||
/* <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> */
|
||||
MenuList_t sg_MoreSetMenuTable[] =
|
||||
{
|
||||
MENU_ITEM_BIND(TEXT_UPGRADE, NULL, NULL, NULL, Hmi_OnCommonFunction, NULL),
|
||||
MENU_ITEM_BIND(TEXT_ABOUT, NULL, NULL, NULL, Hmi_OnCommonFunction, NULL),
|
||||
};
|
||||
|
||||
static void ShowMoreSetMenu(MenuShow_t *ptShowInfo)
|
||||
{
|
||||
uint8_t showNum = 3;
|
||||
uint8_t showsubNum = 3;
|
||||
menusize_t tmpselect;
|
||||
|
||||
MenuShow_t tParentMenuShowInfo;
|
||||
|
||||
if (Menu_QueryParentMenu(&tParentMenuShowInfo, 1) != 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Menu_LimitShowListNum(&tParentMenuShowInfo, &showNum);
|
||||
Menu_LimitShowListNum(ptShowInfo, &showsubNum);
|
||||
|
||||
printf("\e[0;30;46m ------------- %s ------------- \e[0m\n", get_text(tParentMenuShowInfo.uMenuDesc.textId));
|
||||
|
||||
for (int i = 0; i < showNum; i++)
|
||||
{
|
||||
tmpselect = i + tParentMenuShowInfo.showBaseItem;
|
||||
|
||||
if (tmpselect == tParentMenuShowInfo.selectItem)
|
||||
{
|
||||
printf("\e[0;30;47m %d. %-16s\e[0m |", tmpselect + 1, get_text(tParentMenuShowInfo.uItemsListDesc[tmpselect].textId));
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("\e[7;30;47m %d. %-16s\e[0m |", tmpselect + 1, get_text(tParentMenuShowInfo.uItemsListDesc[tmpselect].textId));
|
||||
}
|
||||
|
||||
if (i < showsubNum)
|
||||
{
|
||||
tmpselect = i + ptShowInfo->showBaseItem;
|
||||
|
||||
if (tmpselect == ptShowInfo->selectItem)
|
||||
{
|
||||
printf(" \e[0;30;47m %-14s\e[0m", get_text(ptShowInfo->uItemsListDesc[tmpselect].textId));
|
||||
}
|
||||
else
|
||||
{
|
||||
printf(" \e[7;30;47m %-14s\e[0m", get_text(ptShowInfo->uItemsListDesc[tmpselect].textId));
|
||||
}
|
||||
}
|
||||
|
||||
printf("\n");
|
||||
}
|
||||
}
|
||||
|
||||
void Hmi_MoreSetEnter(const MenuItemInfo_t *pItemInfo)
|
||||
{
|
||||
Menu_Bind(sg_MoreSetMenuTable, MENU_GET_NUM(sg_MoreSetMenuTable), ShowMoreSetMenu);
|
||||
}
|
||||
|
||||
void Hmi_MoreSetLoad(const MenuItemInfo_t *pItemInfo)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void Hmi_MoreSetExit(const MenuItemInfo_t *pItemInfo)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void Hmi_MoreSetTask(const MenuItemInfo_t *pItemInfo)
|
||||
{
|
||||
int cmd;
|
||||
|
||||
printf("%s(0-%s; 1-%s%s; 2-%s; 3-%s; 4-%s): ",
|
||||
get_text(TEXT_SELECT_OPTION), get_text(TEXT_RETURN), get_text(TEXT_RETURN), get_text(TEXT_MAIN_MENU),
|
||||
get_text(TEXT_ENTER), get_text(TEXT_NEXT), get_text(TEXT_PREVIOUS));
|
||||
scanf(" %d", &cmd);
|
||||
|
||||
switch (cmd)
|
||||
{
|
||||
case 0:
|
||||
Menu_Exit(false);
|
||||
break;
|
||||
case 1:
|
||||
Menu_Reset();
|
||||
break;
|
||||
case 2:
|
||||
Menu_Enter();
|
||||
break;
|
||||
case 3:
|
||||
Menu_SelectNext(true);
|
||||
break;
|
||||
case 4:
|
||||
Menu_SelectPrevious(true);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,58 +0,0 @@
|
||||
#include "hmi_music.h"
|
||||
#include "menu.h"
|
||||
#include "language.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
static bool sg_isInit = false;
|
||||
|
||||
const MenuImage_t sgc_MusicImage = {
|
||||
"mmmmmmmmmm",
|
||||
"@"
|
||||
};
|
||||
|
||||
void Hmi_MusicEnter(const MenuItemInfo_t *pItemInfo)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void Hmi_MusicExit(const MenuItemInfo_t *pItemInfo)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void Hmi_MusicLoad(const MenuItemInfo_t *pItemInfo)
|
||||
{
|
||||
sg_isInit = true;
|
||||
}
|
||||
|
||||
void Hmi_MusicTask(const MenuItemInfo_t *pItemInfo)
|
||||
{
|
||||
int cmd = 0;
|
||||
|
||||
if (sg_isInit)
|
||||
{
|
||||
sg_isInit = false;
|
||||
printf("--------------------------\n");
|
||||
printf(" %s%s\n", get_text(pItemInfo->uMenuDesc.textId), get_text(TEXT_FUNCTION_TEST));
|
||||
printf("--------------------------\n");
|
||||
#ifdef _WIN32
|
||||
Sleep(1000);
|
||||
#else
|
||||
sleep(1);
|
||||
#endif
|
||||
}
|
||||
|
||||
printf("%s(0-%s): ",
|
||||
get_text(TEXT_SELECT_OPTION), get_text(TEXT_EXIT));
|
||||
scanf(" %d", &cmd);
|
||||
|
||||
if (cmd == 0)
|
||||
{
|
||||
Menu_Exit(false);
|
||||
}
|
||||
}
|
||||
@ -1,176 +0,0 @@
|
||||
#include "hmi_set.h"
|
||||
#include "hmi_more_set.h"
|
||||
#include "menu.h"
|
||||
#include "language.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
|
||||
const MenuImage_t sgc_SettingImage = {
|
||||
"$$$$$$$$$$",
|
||||
"%"
|
||||
};
|
||||
|
||||
static void OnLanguageFunction(const MenuItemInfo_t *pItemInfo);
|
||||
static void OnBluetoothFunction(const MenuItemInfo_t *pItemInfo);
|
||||
static void OnBatteryFunction(const MenuItemInfo_t *pItemInfo);
|
||||
static void OnStorageFunction(const MenuItemInfo_t *pItemInfo);
|
||||
|
||||
MenuList_t sg_SetMenuTable[] =
|
||||
{
|
||||
MENU_ITEM_BIND(TEXT_LANGUAGE, NULL, NULL, NULL, OnLanguageFunction, NULL),
|
||||
MENU_ITEM_BIND(TEXT_BLUETOOTH, NULL, NULL, NULL, OnBluetoothFunction, NULL),
|
||||
MENU_ITEM_BIND(TEXT_BATTERY, NULL, NULL, NULL, OnBatteryFunction, NULL),
|
||||
MENU_ITEM_BIND(TEXT_STORE, NULL, NULL, NULL, OnStorageFunction, NULL),
|
||||
MENU_ITEM_BIND(TEXT_MORE, Hmi_MoreSetEnter, Hmi_MoreSetExit, Hmi_MoreSetLoad, Hmi_MoreSetTask, NULL),
|
||||
};
|
||||
|
||||
static void ShowSetMenu(MenuShow_t *ptShowInfo)
|
||||
{
|
||||
uint8_t showNum = 3;
|
||||
menusize_t tmpselect;
|
||||
|
||||
Menu_LimitShowListNum(ptShowInfo, &showNum);
|
||||
|
||||
printf("\e[0;30;46m ------------- %s ------------- \e[0m\n", get_text(ptShowInfo->uMenuDesc.textId));
|
||||
|
||||
for (int i = 0; i < showNum; i++)
|
||||
{
|
||||
tmpselect = i + ptShowInfo->showBaseItem;
|
||||
|
||||
if (tmpselect == ptShowInfo->selectItem)
|
||||
{
|
||||
printf("\e[0;30;47m %d. %-16s\e[0m |\n", tmpselect + 1, get_text(ptShowInfo->uItemsListDesc[tmpselect].textId));
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("\e[7;30;47m %d. %-16s\e[0m |\n", tmpselect + 1, get_text(ptShowInfo->uItemsListDesc[tmpselect].textId));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Hmi_SetEnter(const MenuItemInfo_t *pItemInfo)
|
||||
{
|
||||
Menu_Bind(sg_SetMenuTable, MENU_GET_NUM(sg_SetMenuTable), ShowSetMenu);
|
||||
}
|
||||
|
||||
void Hmi_SetLoad(const MenuItemInfo_t *pItemInfo)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void Hmi_SetExit(const MenuItemInfo_t *pItemInfo)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void Hmi_SetTask(const MenuItemInfo_t *pItemInfo)
|
||||
{
|
||||
int cmd;
|
||||
|
||||
printf("%s(0-%s; 1-%s; 2-%s; 3-%s; 4-%s): ",
|
||||
get_text(TEXT_SELECT_OPTION), get_text(TEXT_RETURN), get_text(TEXT_RETURN_MAIN_MENU),
|
||||
get_text(TEXT_ENTER), get_text(TEXT_NEXT), get_text(TEXT_PREVIOUS));
|
||||
scanf(" %d", &cmd);
|
||||
|
||||
switch (cmd)
|
||||
{
|
||||
case 0:
|
||||
Menu_Exit(false);
|
||||
break;
|
||||
case 1:
|
||||
Menu_Reset();
|
||||
break;
|
||||
case 2:
|
||||
Menu_Enter();
|
||||
break;
|
||||
case 3:
|
||||
Menu_SelectNext(true);
|
||||
break;
|
||||
case 4:
|
||||
Menu_SelectPrevious(true);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void OnLanguageFunction(const MenuItemInfo_t *pItemInfo)
|
||||
{
|
||||
int cmd;
|
||||
|
||||
printf("--------------------------\n");
|
||||
printf(" %s%s\n", get_text(TEXT_LANGUAGE), get_text(TEXT_FUNCTION_TEST));
|
||||
printf("--------------------------\n");
|
||||
|
||||
printf("%s(0-中文; 1-English): ",
|
||||
get_text(TEXT_SELECT_OPTION));
|
||||
scanf(" %d", &cmd);
|
||||
|
||||
if (cmd == 0)
|
||||
{
|
||||
set_language(SYSTEM_LANGUAGE_CHINESE);
|
||||
}
|
||||
else
|
||||
{
|
||||
set_language(SYSTEM_LANGUAGE_ENGLISH);
|
||||
}
|
||||
|
||||
Menu_Exit(false);
|
||||
}
|
||||
|
||||
static void OnBluetoothFunction(const MenuItemInfo_t *pItemInfo)
|
||||
{
|
||||
int cmd;
|
||||
|
||||
printf("--------------------------\n");
|
||||
printf(" %s%s\n", get_text(TEXT_BLUETOOTH), get_text(TEXT_FUNCTION_TEST));
|
||||
printf("--------------------------\n");
|
||||
|
||||
printf("%s(0-%s): ",
|
||||
get_text(TEXT_SELECT_OPTION), get_text(TEXT_EXIT));
|
||||
scanf(" %d", &cmd);
|
||||
|
||||
if (cmd == 0)
|
||||
{
|
||||
Menu_Exit(false);
|
||||
}
|
||||
}
|
||||
|
||||
static void OnBatteryFunction(const MenuItemInfo_t *pItemInfo)
|
||||
{
|
||||
int cmd;
|
||||
|
||||
printf("--------------------------\n");
|
||||
printf(" %s%s\n", get_text(TEXT_BATTERY), get_text(TEXT_FUNCTION_TEST));
|
||||
printf("--------------------------\n");
|
||||
|
||||
printf("%s(0-%s): ",
|
||||
get_text(TEXT_SELECT_OPTION), get_text(TEXT_EXIT));
|
||||
scanf(" %d", &cmd);
|
||||
|
||||
if (cmd == 0)
|
||||
{
|
||||
Menu_Exit(false);
|
||||
}
|
||||
}
|
||||
|
||||
static void OnStorageFunction(const MenuItemInfo_t *pItemInfo)
|
||||
{
|
||||
int cmd;
|
||||
|
||||
printf("--------------------------\n");
|
||||
printf(" %s%s\n", get_text(TEXT_STORE), get_text(TEXT_FUNCTION_TEST));
|
||||
printf("--------------------------\n");
|
||||
|
||||
printf("%s(0-%s): ",
|
||||
get_text(TEXT_SELECT_OPTION), get_text(TEXT_EXIT));
|
||||
scanf(" %d", &cmd);
|
||||
|
||||
if (cmd == 0)
|
||||
{
|
||||
Menu_Exit(false);
|
||||
}
|
||||
}
|
||||
@ -1,67 +0,0 @@
|
||||
#include "hmi_video.h"
|
||||
#include "menu.h"
|
||||
#include "language.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
const MenuImage_t sgc_VideoImage = {
|
||||
"vvvvvvvvvv",
|
||||
"#"
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
void Hmi_VideoLoad(const MenuItemInfo_t *pItemInfo)
|
||||
{
|
||||
printf("--------------------------\n");
|
||||
printf(" %s %s...\n", get_text(pItemInfo->uMenuDesc.textId), get_text(TEXT_LOADING));
|
||||
printf("--------------------------\n");
|
||||
#ifdef _WIN32
|
||||
Sleep(1000);
|
||||
#else
|
||||
sleep(1);
|
||||
#endif
|
||||
CLEAR();
|
||||
MOVETO(0, 0);
|
||||
}
|
||||
|
||||
void Hmi_VideoExit(const MenuItemInfo_t *pItemInfo)
|
||||
{
|
||||
CLEAR();
|
||||
MOVETO(0, 0);
|
||||
|
||||
printf("--------------------------\n");
|
||||
printf(" %s %s...\n", get_text(pItemInfo->uMenuDesc.textId), get_text(TEXT_EXIT));
|
||||
printf("--------------------------\n");
|
||||
#ifdef _WIN32
|
||||
Sleep(1000);
|
||||
#else
|
||||
sleep(1);
|
||||
#endif
|
||||
}
|
||||
|
||||
void Hmi_VideoTask(const MenuItemInfo_t *pItemInfo)
|
||||
{
|
||||
int cmd;
|
||||
|
||||
printf("--------------------------\n");
|
||||
printf(" %s%s\n", get_text(pItemInfo->uMenuDesc.textId), get_text(TEXT_FUNCTION_TEST));
|
||||
printf("--------------------------\n");
|
||||
|
||||
printf("%s(0-%s): ",
|
||||
get_text(TEXT_SELECT_OPTION), get_text(TEXT_EXIT));
|
||||
scanf(" %d", &cmd);
|
||||
|
||||
|
||||
|
||||
if (cmd == 0)
|
||||
{
|
||||
Menu_Exit(false);
|
||||
}
|
||||
}
|
||||
@ -1,140 +0,0 @@
|
||||
#include "mainhmi.h"
|
||||
#include "hmi_camera.h"
|
||||
#include "hmi_music.h"
|
||||
#include "hmi_set.h"
|
||||
#include "hmi_video.h"
|
||||
#include "hmi_common.h"
|
||||
#include "menu.h"
|
||||
#include "language.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
|
||||
|
||||
MenuList_t sg_MainMenuTable[] =
|
||||
{
|
||||
MENU_ITEM_BIND(TEXT_MUSIC, Hmi_MusicEnter, Hmi_MusicExit, Hmi_MusicLoad, Hmi_MusicTask, (MenuImage_t *)&sgc_MusicImage),
|
||||
|
||||
MENU_ITEM_BIND(TEXT_VIDEO, NULL, Hmi_VideoExit, Hmi_VideoLoad, Hmi_VideoTask, (MenuImage_t *)&sgc_VideoImage),
|
||||
|
||||
MENU_ITEM_BIND(TEXT_CAMERA, Hmi_CameraEnter, Hmi_CameraExit, Hmi_CameraLoad, Hmi_CameraTask, (MenuImage_t *)&sgc_CameraImage),
|
||||
|
||||
MENU_ITEM_BIND(TEXT_SETTING, Hmi_SetEnter, Hmi_SetExit, Hmi_SetLoad, Hmi_SetTask, (MenuImage_t *)&sgc_SettingImage),
|
||||
};
|
||||
|
||||
|
||||
|
||||
static void ShowMainMenu(MenuShow_t *ptShowInfo)
|
||||
{
|
||||
uint8_t showNum = 3;
|
||||
MenuImage_t *pMenuImage;
|
||||
menusize_t tmpselect;
|
||||
|
||||
Menu_LimitShowListNum(ptShowInfo, &showNum);
|
||||
|
||||
printf("\e[0;30;47m ------------- %s ------------- \e[0m\n", get_text(ptShowInfo->uMenuDesc.textId));
|
||||
|
||||
for (int i = 0; i < showNum; i++)
|
||||
{
|
||||
tmpselect = i + ptShowInfo->showBaseItem;
|
||||
pMenuImage = (MenuImage_t *)ptShowInfo->pItemsListExtendData[tmpselect];
|
||||
|
||||
if (tmpselect == ptShowInfo->selectItem)
|
||||
{
|
||||
printf("\e[0;30;47m %-10s \e[0m", pMenuImage->pImageFrame);
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("\e[7;30;47m %-10s \e[0m", pMenuImage->pImageFrame);
|
||||
}
|
||||
}
|
||||
|
||||
printf("\n");
|
||||
|
||||
for (int i = 0; i < showNum; i++)
|
||||
{
|
||||
tmpselect = i + ptShowInfo->showBaseItem;
|
||||
pMenuImage = (MenuImage_t *)ptShowInfo->pItemsListExtendData[tmpselect];
|
||||
|
||||
if (tmpselect == ptShowInfo->selectItem)
|
||||
{
|
||||
printf("\e[0;30;47m %-s%-8s%-s \e[0m", pMenuImage->pImage, get_text(ptShowInfo->uItemsListDesc[tmpselect].textId), pMenuImage->pImage);
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("\e[7;30;47m %-s%-8s%-s \e[0m", pMenuImage->pImage, get_text(ptShowInfo->uItemsListDesc[tmpselect].textId), pMenuImage->pImage);
|
||||
}
|
||||
}
|
||||
|
||||
printf("\n");
|
||||
|
||||
for (int i = 0; i < showNum; i++)
|
||||
{
|
||||
tmpselect = i + ptShowInfo->showBaseItem;
|
||||
pMenuImage = (MenuImage_t *)ptShowInfo->pItemsListExtendData[tmpselect];
|
||||
|
||||
if (tmpselect == ptShowInfo->selectItem)
|
||||
{
|
||||
printf("\e[0;30;47m %-10s \e[0m", pMenuImage->pImageFrame);
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("\e[7;30;47m %-10s \e[0m", pMenuImage->pImageFrame);
|
||||
}
|
||||
}
|
||||
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
void Hmi_LoadMainHmi(const MenuItemInfo_t *pItemInfo)
|
||||
{
|
||||
Menu_Bind(sg_MainMenuTable, MENU_GET_NUM(sg_MainMenuTable), ShowMainMenu);
|
||||
}
|
||||
|
||||
void Hmi_MainTask(const MenuItemInfo_t *pItemInfo)
|
||||
{
|
||||
int cmd;
|
||||
|
||||
printf("%s(0-%s; 2-%s; 3-%s; 4-%s; 5-%s(%s); 6-%s(%s); 7-%s(%s)): ",
|
||||
get_text(TEXT_SELECT_OPTION), get_text(TEXT_EXIT_MAIN_MENU),
|
||||
get_text(TEXT_ENTER), get_text(TEXT_NEXT), get_text(TEXT_PREVIOUS),
|
||||
get_text(TEXT_MUSIC), get_text(TEXT_SHORTCUT),
|
||||
get_text(TEXT_LANGUAGE), get_text(TEXT_SHORTCUT),
|
||||
get_text(TEXT_MORE_SETTING), get_text(TEXT_SHORTCUT));
|
||||
scanf(" %d", &cmd);
|
||||
|
||||
switch (cmd)
|
||||
{
|
||||
case 0:
|
||||
Menu_MainExit();
|
||||
break;
|
||||
case 1:
|
||||
Menu_Reset();
|
||||
break;
|
||||
case 2:
|
||||
Menu_Enter();
|
||||
break;
|
||||
case 3:
|
||||
Menu_SelectNext(true);
|
||||
break;
|
||||
case 4:
|
||||
Menu_SelectPrevious(true);
|
||||
break;
|
||||
|
||||
case 5:
|
||||
Menu_ShortcutEnter(true, 1, 0);
|
||||
break;
|
||||
|
||||
case 6:
|
||||
Menu_ShortcutEnter(true, 2, 3, 0);
|
||||
break;
|
||||
|
||||
case 7:
|
||||
Menu_ShortcutEnter(true, 2, 3, 4);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,70 +0,0 @@
|
||||
#include "language.h"
|
||||
|
||||
static SystemLanguage_e sg_eSystemLanguage = SYSTEM_LANGUAGE_CHINESE;
|
||||
|
||||
const char *(sg_kSystemLanguage[TEXT_ALL])[SYSTEM_LANGUAGE_ALL] =
|
||||
{
|
||||
[TEXT_MENU] = {"菜单", "menu"},
|
||||
[TEXT_MAIN_MENU] = {"主菜单", "main menu"},
|
||||
[TEXT_SELECT_OPTION] = {"选择操作", "select option"},
|
||||
[TEXT_ENTER] = {"进入", "enter"},
|
||||
[TEXT_EXIT] = {"退出", "exit"},
|
||||
[TEXT_RETURN] = {"返回", "return"},
|
||||
[TEXT_MUSIC] = {"音乐", "music"},
|
||||
[TEXT_VIDEO] = {"视频", "video"},
|
||||
[TEXT_CAMERA] = {"摄像机", "camera"},
|
||||
[TEXT_SETTING] = {"设置", "setting"},
|
||||
[TEXT_SHORTCUT] = {"快捷", "shortcut"},
|
||||
[TEXT_MORE] = {"更多", "more"},
|
||||
[TEXT_LANGUAGE] = {"语言", "language"},
|
||||
[TEXT_NEXT] = {"下一个", "next"},
|
||||
[TEXT_PREVIOUS] = {"上一个", "previous"},
|
||||
[TEXT_FUNCTION_TEST] = {"功能测试", "functional testing"},
|
||||
[TEXT_TEST] = {"测试", "test"},
|
||||
[TEXT_BLUETOOTH] = {"蓝牙", "bluetooth"},
|
||||
[TEXT_BATTERY] = {"电池", "battery"},
|
||||
[TEXT_STORE] = {"储存", "store"},
|
||||
[TEXT_UPGRADE] = {"升级", "upgrade"},
|
||||
[TEXT_ABOUT] = {"关于", "about"},
|
||||
[TEXT_PHOTO] = {"拍照", "Photo"},
|
||||
[TEXT_PHOTOGRAPHY] = {"摄影", "photography"},
|
||||
[TEXT_LOADING] = {"加载中", "loading"},
|
||||
[TEXT_STOPPING_PLAYBACK] = {"正在停止播放", "stopping playback"},
|
||||
[TEXT_EXIT_MAIN_MENU] = {"退出主菜单", "exit main menu"},
|
||||
[TEXT_RETURN_MAIN_MENU] = {"返回主菜单", "return main menu"},
|
||||
[TEXT_MORE_SETTING] = {"更多设置", "more setting"},
|
||||
|
||||
};
|
||||
|
||||
void set_language(SystemLanguage_e lang)
|
||||
{
|
||||
if (lang >= 0 && lang < SYSTEM_LANGUAGE_ALL)
|
||||
{
|
||||
sg_eSystemLanguage = lang;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const char *get_text(TextId_e id)
|
||||
{
|
||||
static const char *pszNullString = "N/A";
|
||||
|
||||
if (id >= 0 && id < TEXT_ALL)
|
||||
{
|
||||
return sg_kSystemLanguage[id][sg_eSystemLanguage];
|
||||
}
|
||||
|
||||
return pszNullString; // 未找到对应的文本
|
||||
}
|
||||
|
||||
const char *get_text_by_language(SystemLanguage_e lang, TextId_e id)
|
||||
{
|
||||
static const char *pszNullString = "N/A";
|
||||
|
||||
if (id >= 0 && id < TEXT_ALL)
|
||||
{
|
||||
return sg_kSystemLanguage[id][lang];
|
||||
}
|
||||
|
||||
return pszNullString; // 未找到对应的文本
|
||||
}
|
||||
@ -1,52 +0,0 @@
|
||||
#ifndef LANGUAGE_H
|
||||
#define LANGUAGE_H
|
||||
|
||||
typedef enum
|
||||
{
|
||||
SYSTEM_LANGUAGE_CHINESE = 0,
|
||||
SYSTEM_LANGUAGE_ENGLISH,
|
||||
|
||||
SYSTEM_LANGUAGE_ALL,
|
||||
} SystemLanguage_e;
|
||||
|
||||
|
||||
typedef enum
|
||||
{
|
||||
TEXT_MENU = 0, // 菜单
|
||||
TEXT_MAIN_MENU, // 主菜单
|
||||
TEXT_SELECT_OPTION, // 选择操作
|
||||
TEXT_ENTER, // 进入
|
||||
TEXT_EXIT, // 退出
|
||||
TEXT_RETURN, // 返回
|
||||
TEXT_MUSIC, // 音乐
|
||||
TEXT_VIDEO, // 视频
|
||||
TEXT_CAMERA, // 摄像机
|
||||
TEXT_SETTING, // 设置
|
||||
TEXT_SHORTCUT, // 快捷
|
||||
TEXT_MORE, // 更多
|
||||
TEXT_LANGUAGE, // 语言
|
||||
TEXT_NEXT, // 下一个
|
||||
TEXT_PREVIOUS, // 上一个
|
||||
TEXT_FUNCTION_TEST, // 功能测试
|
||||
TEXT_TEST, // 测试
|
||||
TEXT_BLUETOOTH, // 蓝牙
|
||||
TEXT_BATTERY, // 电池
|
||||
TEXT_STORE, // 储存
|
||||
TEXT_UPGRADE, // 升级
|
||||
TEXT_ABOUT, // 关于
|
||||
TEXT_PHOTO, // 拍照
|
||||
TEXT_PHOTOGRAPHY, // 摄影
|
||||
TEXT_LOADING, // 加载中
|
||||
TEXT_STOPPING_PLAYBACK, // 正在停止播放
|
||||
TEXT_EXIT_MAIN_MENU, // 退出主菜单
|
||||
TEXT_RETURN_MAIN_MENU, // 返回主菜单
|
||||
TEXT_MORE_SETTING, // 更多设置
|
||||
|
||||
TEXT_ALL,
|
||||
} TextId_e;
|
||||
|
||||
void set_language(SystemLanguage_e lang);
|
||||
const char *get_text(TextId_e id);
|
||||
const char *get_text_by_language(SystemLanguage_e lang, TextId_e id);
|
||||
|
||||
#endif
|
||||
Binary file not shown.
319
port/menu_port.c
319
port/menu_port.c
@ -1,319 +0,0 @@
|
||||
/**
|
||||
**********************************************************************************************************************
|
||||
* @file menu_port.c
|
||||
* @brief 菜单组件硬件端口层实现
|
||||
* @author menu_component
|
||||
* @date 2025-12-19
|
||||
**********************************************************************************************************************
|
||||
*/
|
||||
|
||||
/* Includes ----------------------------------------------------------------------------------------------------------*/
|
||||
#include "menu_port.h"
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
|
||||
/* 全局变量 ---------------------------------------------------------------------------------------------------------*/
|
||||
static const MenuPortDriver* sg_menu_port_driver = NULL;
|
||||
|
||||
/* 默认硬件驱动实现 -------------------------------------------------------------------------------------------------*/
|
||||
|
||||
/**
|
||||
* @brief 默认打印函数
|
||||
* @param fmt 格式化字符串
|
||||
* @param args 可变参数列表
|
||||
*/
|
||||
static void menu_port_default_printf(const char* fmt, va_list args)
|
||||
{
|
||||
// 默认不实现打印功能
|
||||
(void)fmt;
|
||||
(void)args;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 默认获取系统时间函数
|
||||
* @return 系统时间(ms)
|
||||
*/
|
||||
static uint32_t menu_port_default_get_tick(void)
|
||||
{
|
||||
// 默认返回0,用户需要根据实际硬件实现
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 默认延迟函数
|
||||
* @param ms 延迟时间(ms)
|
||||
*/
|
||||
static void menu_port_default_delay_ms(uint32_t ms)
|
||||
{
|
||||
// 默认实现简单的延迟
|
||||
for (uint32_t i = 0; i < ms * 1000; i++) {
|
||||
__asm__ volatile ("nop");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 默认显示函数
|
||||
* @param menu_name 菜单名称
|
||||
* @param menu_id 菜单ID
|
||||
*/
|
||||
static void menu_port_default_display(const char* menu_name, uint16_t menu_id)
|
||||
{
|
||||
// 默认不实现显示功能
|
||||
(void)menu_name;
|
||||
(void)menu_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 默认按键扫描函数
|
||||
* @return 按键事件类型
|
||||
*/
|
||||
static MenuEventType menu_port_default_key_scan(void)
|
||||
{
|
||||
// 默认返回无事件
|
||||
return MENU_EVENT_NONE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 默认中断管理函数
|
||||
* @param enable 是否启用中断
|
||||
*/
|
||||
static void menu_port_default_irq_ctrl(bool enable)
|
||||
{
|
||||
// 默认不实现中断管理
|
||||
(void)enable;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 默认错误处理函数
|
||||
* @param err_code 错误码
|
||||
*/
|
||||
static void menu_port_default_error_handler(MenuErrCode err_code)
|
||||
{
|
||||
// 默认不实现错误处理
|
||||
(void)err_code;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 默认Modbus发送函数
|
||||
* @param reg_type 寄存器类型
|
||||
* @param reg_addr 寄存器地址
|
||||
* @param reg_buf 寄存器数据
|
||||
* @param buf_len 数据长度
|
||||
* @return 错误码
|
||||
*/
|
||||
static MenuErrCode menu_port_default_modbus_send(ModbusRegType reg_type, uint16_t reg_addr, const uint8_t* reg_buf, uint8_t buf_len)
|
||||
{
|
||||
// 默认不实现Modbus发送
|
||||
(void)reg_type;
|
||||
(void)reg_addr;
|
||||
(void)reg_buf;
|
||||
(void)buf_len;
|
||||
return MENU_ERR_OPERATION_FAILED;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 默认Modbus接收函数
|
||||
* @param reg_type 寄存器类型
|
||||
* @param reg_addr 寄存器地址
|
||||
* @param reg_buf 寄存器数据
|
||||
* @param buf_len 数据长度
|
||||
* @return 错误码
|
||||
*/
|
||||
static MenuErrCode menu_port_default_modbus_receive(ModbusRegType reg_type, uint16_t reg_addr, uint8_t* reg_buf, uint8_t buf_len)
|
||||
{
|
||||
// 默认不实现Modbus接收
|
||||
(void)reg_type;
|
||||
(void)reg_addr;
|
||||
(void)reg_buf;
|
||||
(void)buf_len;
|
||||
return MENU_ERR_OPERATION_FAILED;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 默认持久化保存函数
|
||||
* @param data 要保存的数据
|
||||
* @param size 数据大小
|
||||
* @return 错误码
|
||||
*/
|
||||
static MenuErrCode menu_port_default_persistence_save(const uint8_t* data, uint16_t size)
|
||||
{
|
||||
// 默认不实现持久化保存功能
|
||||
(void)data;
|
||||
(void)size;
|
||||
return MENU_ERR_OPERATION_FAILED;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 默认持久化恢复函数
|
||||
* @param data 恢复数据缓冲区
|
||||
* @param size 数据大小
|
||||
* @return 错误码
|
||||
*/
|
||||
static MenuErrCode menu_port_default_persistence_restore(uint8_t* data, uint16_t* size)
|
||||
{
|
||||
// 默认不实现持久化恢复功能
|
||||
(void)data;
|
||||
(void)size;
|
||||
return MENU_ERR_OPERATION_FAILED;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 默认硬件驱动
|
||||
*/
|
||||
static const MenuPortDriver sg_default_driver = {
|
||||
.printf = menu_port_default_printf,
|
||||
.get_tick = menu_port_default_get_tick,
|
||||
.delay_ms = menu_port_default_delay_ms,
|
||||
.display = menu_port_default_display,
|
||||
.key_scan = menu_port_default_key_scan,
|
||||
.irq_ctrl = menu_port_default_irq_ctrl,
|
||||
.error_handler = menu_port_default_error_handler,
|
||||
.modbus_send = menu_port_default_modbus_send,
|
||||
.modbus_receive = menu_port_default_modbus_receive,
|
||||
.persistence_save = menu_port_default_persistence_save,
|
||||
.persistence_restore = menu_port_default_persistence_restore,
|
||||
};
|
||||
|
||||
/* 函数实现 ---------------------------------------------------------------------------------------------------------*/
|
||||
|
||||
/**
|
||||
* @brief 初始化硬件端口层
|
||||
* @param driver 硬件驱动结构体指针
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_port_init(const MenuPortDriver* driver)
|
||||
{
|
||||
if (driver != NULL) {
|
||||
sg_menu_port_driver = driver;
|
||||
} else {
|
||||
sg_menu_port_driver = &sg_default_driver;
|
||||
}
|
||||
return MENU_ERR_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 反初始化硬件端口层
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_port_deinit(void)
|
||||
{
|
||||
sg_menu_port_driver = NULL;
|
||||
return MENU_ERR_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取当前系统时间(ms)
|
||||
* @return 系统时间(ms)
|
||||
*/
|
||||
uint32_t menu_port_get_tick(void)
|
||||
{
|
||||
if (sg_menu_port_driver != NULL && sg_menu_port_driver->get_tick != NULL) {
|
||||
return sg_menu_port_driver->get_tick();
|
||||
}
|
||||
return menu_port_default_get_tick();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 硬件延迟函数
|
||||
* @param ms 延迟时间(ms)
|
||||
*/
|
||||
void menu_port_delay_ms(uint32_t ms)
|
||||
{
|
||||
if (sg_menu_port_driver != NULL && sg_menu_port_driver->delay_ms != NULL) {
|
||||
sg_menu_port_driver->delay_ms(ms);
|
||||
} else {
|
||||
menu_port_default_delay_ms(ms);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 打印函数
|
||||
* @param fmt 格式化字符串
|
||||
* @param ... 可变参数
|
||||
*/
|
||||
void menu_port_printf(const char* fmt, ...)
|
||||
{
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
|
||||
if (sg_menu_port_driver != NULL && sg_menu_port_driver->printf != NULL) {
|
||||
sg_menu_port_driver->printf(fmt, args);
|
||||
} else {
|
||||
menu_port_default_printf(fmt, args);
|
||||
}
|
||||
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 菜单显示函数
|
||||
* @param menu_name 菜单名称
|
||||
* @param menu_id 菜单ID
|
||||
*/
|
||||
void menu_port_display(const char* menu_name, uint16_t menu_id)
|
||||
{
|
||||
if (sg_menu_port_driver != NULL && sg_menu_port_driver->display != NULL) {
|
||||
sg_menu_port_driver->display(menu_name, menu_id);
|
||||
} else {
|
||||
menu_port_default_display(menu_name, menu_id);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 按键扫描函数
|
||||
* @return 按键事件类型
|
||||
*/
|
||||
MenuEventType menu_port_key_scan(void)
|
||||
{
|
||||
if (sg_menu_port_driver != NULL && sg_menu_port_driver->key_scan != NULL) {
|
||||
return sg_menu_port_driver->key_scan();
|
||||
}
|
||||
return menu_port_default_key_scan();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 中断管理函数
|
||||
* @param enable 是否启用中断
|
||||
*/
|
||||
void menu_port_irq_ctrl(bool enable)
|
||||
{
|
||||
if (sg_menu_port_driver != NULL && sg_menu_port_driver->irq_ctrl != NULL) {
|
||||
sg_menu_port_driver->irq_ctrl(enable);
|
||||
} else {
|
||||
menu_port_default_irq_ctrl(enable);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 错误处理函数
|
||||
* @param err_code 错误码
|
||||
*/
|
||||
void menu_port_error_handler(MenuErrCode err_code)
|
||||
{
|
||||
if (sg_menu_port_driver != NULL && sg_menu_port_driver->error_handler != NULL) {
|
||||
sg_menu_port_driver->error_handler(err_code);
|
||||
} else {
|
||||
menu_port_default_error_handler(err_code);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 断言失败处理函数
|
||||
* @param expr 断言表达式
|
||||
* @param file 文件名
|
||||
* @param line 行号
|
||||
*/
|
||||
void menu_assert_failed(const char* expr, const char* file, uint32_t line)
|
||||
{
|
||||
// 打印断言失败信息
|
||||
char buf[128] = {0};
|
||||
snprintf(buf, sizeof(buf), "Assertion failed: %s, file %s, line %lu\n", expr, file, (unsigned long)line);
|
||||
|
||||
// 直接使用printf输出,因为menu_port_printf需要va_list参数
|
||||
printf("%s", buf);
|
||||
|
||||
// 进入死循环
|
||||
while (1) {
|
||||
// 可以添加其他处理逻辑,如复位系统等
|
||||
}
|
||||
}
|
||||
111
port/menu_port.h
111
port/menu_port.h
@ -1,111 +0,0 @@
|
||||
/**
|
||||
**********************************************************************************************************************
|
||||
* @file menu_port.h
|
||||
* @brief 菜单组件硬件端口层定义
|
||||
* @author menu_component
|
||||
* @date 2025-12-19
|
||||
**********************************************************************************************************************
|
||||
*/
|
||||
|
||||
#ifndef MENU_PORT_H
|
||||
#define MENU_PORT_H
|
||||
|
||||
/* Includes ----------------------------------------------------------------------------------------------------------*/
|
||||
#include "../src/core/menu_config.h"
|
||||
#include <stdarg.h>
|
||||
|
||||
/* 类型定义 ---------------------------------------------------------------------------------------------------------*/
|
||||
|
||||
/**
|
||||
* @brief Modbus寄存器类型
|
||||
*/
|
||||
typedef enum {
|
||||
MODBUS_REG_TYPE_COIL, /*!< 线圈寄存器 */
|
||||
MODBUS_REG_TYPE_DISCRETE_INPUT, /*!< 离散输入寄存器 */
|
||||
MODBUS_REG_TYPE_HOLDING_REG, /*!< 保持寄存器 */
|
||||
MODBUS_REG_TYPE_INPUT_REG, /*!< 输入寄存器 */
|
||||
} ModbusRegType;
|
||||
|
||||
/**
|
||||
* @brief 持久化操作回调函数类型定义
|
||||
*/
|
||||
typedef MenuErrCode (*MenuPersistenceSaveCallback)(const uint8_t* data, uint16_t size); ///< 保存持久化数据回调
|
||||
typedef MenuErrCode (*MenuPersistenceRestoreCallback)(uint8_t* data, uint16_t* size); ///< 恢复持久化数据回调
|
||||
|
||||
/**
|
||||
* @brief 硬件驱动结构体
|
||||
*/
|
||||
typedef struct {
|
||||
void (*printf)(const char* fmt, va_list args); ///< 硬件打印接口
|
||||
uint32_t (*get_tick)(void); ///< 获取系统滴答时间
|
||||
void (*delay_ms)(uint32_t ms); ///< 硬件延迟函数
|
||||
void (*display)(const char* menu_name, uint16_t menu_id); ///< 菜单显示接口
|
||||
MenuEventType (*key_scan)(void); ///< 按键扫描接口(可选)
|
||||
void (*irq_ctrl)(bool enable); ///< 中断管理接口(可选)
|
||||
void (*error_handler)(MenuErrCode err_code); ///< 错误处理接口(可选)
|
||||
MenuErrCode (*modbus_send)(ModbusRegType reg_type, uint16_t reg_addr, const uint8_t* reg_buf, uint8_t buf_len); ///< Modbus发送接口(可选)
|
||||
MenuErrCode (*modbus_receive)(ModbusRegType reg_type, uint16_t reg_addr, uint8_t* reg_buf, uint8_t buf_len); ///< Modbus接收接口(可选)
|
||||
MenuPersistenceSaveCallback persistence_save; ///< 持久化数据保存接口(可选)
|
||||
MenuPersistenceRestoreCallback persistence_restore; ///< 持久化数据恢复接口(可选)
|
||||
} MenuPortDriver;
|
||||
|
||||
/* 函数声明 ---------------------------------------------------------------------------------------------------------*/
|
||||
|
||||
/**
|
||||
* @brief 初始化硬件端口层
|
||||
* @param driver 硬件驱动结构体指针
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_port_init(const MenuPortDriver* driver);
|
||||
|
||||
/**
|
||||
* @brief 反初始化硬件端口层
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_port_deinit(void);
|
||||
|
||||
/**
|
||||
* @brief 获取当前系统时间(ms)
|
||||
* @return 系统时间(ms)
|
||||
*/
|
||||
uint32_t menu_port_get_tick(void);
|
||||
|
||||
/**
|
||||
* @brief 硬件延迟函数
|
||||
* @param ms 延迟时间(ms)
|
||||
*/
|
||||
void menu_port_delay_ms(uint32_t ms);
|
||||
|
||||
/**
|
||||
* @brief 打印函数
|
||||
* @param fmt 格式化字符串
|
||||
* @param ... 可变参数
|
||||
*/
|
||||
void menu_port_printf(const char* fmt, ...);
|
||||
|
||||
/**
|
||||
* @brief 菜单显示函数
|
||||
* @param menu_name 菜单名称
|
||||
* @param menu_id 菜单ID
|
||||
*/
|
||||
void menu_port_display(const char* menu_name, uint16_t menu_id);
|
||||
|
||||
/**
|
||||
* @brief 按键扫描函数
|
||||
* @return 按键事件类型
|
||||
*/
|
||||
MenuEventType menu_port_key_scan(void);
|
||||
|
||||
/**
|
||||
* @brief 中断管理函数
|
||||
* @param enable 是否启用中断
|
||||
*/
|
||||
void menu_port_irq_ctrl(bool enable);
|
||||
|
||||
/**
|
||||
* @brief 错误处理函数
|
||||
* @param err_code 错误码
|
||||
*/
|
||||
void menu_port_error_handler(MenuErrCode err_code);
|
||||
|
||||
#endif /* MENU_PORT_H */
|
||||
@ -1,107 +0,0 @@
|
||||
/**
|
||||
**********************************************************************************************************************
|
||||
* @file menu_config.h
|
||||
* @brief 菜单组件核心配置文件
|
||||
* @author menu_component
|
||||
* @date 2025-12-19
|
||||
**********************************************************************************************************************
|
||||
*/
|
||||
|
||||
#ifndef MENU_CONFIG_H
|
||||
#define MENU_CONFIG_H
|
||||
|
||||
/* Includes ----------------------------------------------------------------------------------------------------------*/
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
/* 配置项 -----------------------------------------------------------------------------------------------------------*/
|
||||
|
||||
/* 菜单节点配置 */
|
||||
#define MENU_CONFIG_MAX_NODES 32U /* 最大菜单节点数 */
|
||||
#define MENU_CONFIG_HASH_TABLE_SIZE 31U /* 哈希表大小(必须为质数) */
|
||||
#define MENU_CONFIG_STACK_DEPTH 8U /* 菜单栈深度 */
|
||||
#define MENU_CONFIG_MAX_STATE_TRANSITIONS 16U /* 最大自定义状态转换规则数 */
|
||||
|
||||
/* 事件队列配置 */
|
||||
#define MENU_CONFIG_EVENT_QUEUE_LEN 16U /* 事件队列长度 */
|
||||
#define MENU_CONFIG_EVENT_MAX_PRIORITY 4U /* 事件最大优先级(0-3) */
|
||||
|
||||
/* 功能开关配置 */
|
||||
#define MENU_CONFIG_ENABLE_ASSERT 1U /* 是否启用断言 */
|
||||
#define MENU_CONFIG_ENABLE_DEBUG 1U /* 是否启用调试打印 */
|
||||
#define MENU_CONFIG_ENABLE_MEM_MONITOR 1U /* 是否启用内存监控 */
|
||||
#define MENU_CONFIG_ENABLE_PARAM 1U /* 是否启用参数管理 */
|
||||
#define MENU_CONFIG_ENABLE_LANG 1U /* 是否启用多语言支持 */
|
||||
#define MENU_CONFIG_ENABLE_MODBUS_MAP 1U /* 是否启用Modbus映射 */
|
||||
#define MENU_CONFIG_ENABLE_STATE_MACHINE_EXT 1U /* 是否启用状态机扩展 */
|
||||
#define MENU_CONFIG_ENABLE_PERMISSION 1U /* 是否启用权限管理 */
|
||||
#define MENU_CONFIG_ENABLE_PERSISTENCE 1U /* 是否启用状态持久化 */
|
||||
|
||||
/* 权限管理配置 */
|
||||
#define MENU_CONFIG_PERMISSION_MAX_ROLES 8U /* 最大支持的角色数量 */
|
||||
#define MENU_CONFIG_PERMISSION_MAX_LEVEL 16U /* 最大权限级别 */
|
||||
|
||||
/* 状态持久化配置 */
|
||||
#define MENU_CONFIG_PERSISTENCE_MAX_SIZE 256U /* 持久化数据的最大大小(字节) */
|
||||
#define MENU_CONFIG_PERSISTENCE_AUTO_SAVE_INTERVAL 5000U /* 自动保存间隔(ms) */
|
||||
#define MENU_CONFIG_PERSISTENCE_ENABLE_ENCRYPT 1U /* 是否启用持久化数据加密 */
|
||||
|
||||
/* 类型定义 ---------------------------------------------------------------------------------------------------------*/
|
||||
typedef uint16_t MenuNodeId; /* 菜单节点ID类型 */
|
||||
typedef uint8_t MenuState; /* 菜单状态类型 */
|
||||
typedef uint8_t MenuEventType; /* 事件类型 */
|
||||
typedef uint16_t MenuErrCode; /* 错误码类型 */
|
||||
|
||||
/* 状态定义 ---------------------------------------------------------------------------------------------------------*/
|
||||
#define MENU_STATE_INIT 0U /* 初始化状态 */
|
||||
#define MENU_STATE_NORMAL 1U /* 正常导航状态 */
|
||||
#define MENU_STATE_PARAM_EDIT 2U /* 参数编辑状态 */
|
||||
#define MENU_STATE_CONFIRM 3U /* 确认状态 */
|
||||
#define MENU_STATE_ERROR 4U /* 错误状态 */
|
||||
|
||||
/* 事件类型定义 -----------------------------------------------------------------------------------------------------*/
|
||||
#define MENU_EVENT_NONE 0U /* 无事件 */
|
||||
#define MENU_EVENT_KEY_UP 1U /* 上键事件 */
|
||||
#define MENU_EVENT_KEY_DOWN 2U /* 下键事件 */
|
||||
#define MENU_EVENT_KEY_ENTER 3U /* 确认键事件 */
|
||||
#define MENU_EVENT_KEY_ESC 4U /* 退出键事件 */
|
||||
#define MENU_EVENT_TIMEOUT 5U /* 超时事件 */
|
||||
#define MENU_EVENT_CUSTOM_BASE 10U /* 自定义事件基值 */
|
||||
|
||||
/* 错误码定义 -------------------------------------------------------------------------------------------------------*/
|
||||
#define MENU_ERR_OK 0U /* 成功 */
|
||||
#define MENU_ERR_INVALID_PARAM 1U /* 参数无效 */
|
||||
#define MENU_ERR_OUT_OF_MEMORY 2U /* 内存不足 */
|
||||
#define MENU_ERR_NODE_NOT_FOUND 3U /* 节点未找到 */
|
||||
#define MENU_ERR_QUEUE_FULL 4U /* 队列已满 */
|
||||
#define MENU_ERR_QUEUE_EMPTY 5U /* 队列为空 */
|
||||
#define MENU_ERR_INVALID_STATE 6U /* 无效状态 */
|
||||
#define MENU_ERR_OPERATION_FAILED 7U /* 操作失败 */
|
||||
#define MENU_ERR_NOT_INITIALIZED 8U /* 未初始化 */
|
||||
#define MENU_ERR_ALREADY_EXISTS 9U /* 已存在 */
|
||||
#define MENU_ERR_STACK_OVERFLOW 10U /* 栈溢出 */
|
||||
#define MENU_ERR_STACK_UNDERFLOW 11U /* 栈下溢 */
|
||||
#define MENU_ERR_STACK_EMPTY 12U /* 栈为空 */
|
||||
#define MENU_ERR_NAV_PATH_OVERFLOW 13U /* 导航路径溢出 */
|
||||
#define MENU_ERR_INVALID_CALLBACK 14U /* 无效回调函数 */
|
||||
#define MENU_ERR_INVALID_TRANSITION 15U /* 无效状态转换 */
|
||||
#define MENU_ERR_NOT_SUPPORTED 16U /* 不支持的操作 */
|
||||
|
||||
/* 断言定义 ---------------------------------------------------------------------------------------------------------*/
|
||||
#if MENU_CONFIG_ENABLE_ASSERT
|
||||
extern void menu_assert_failed(const char* expr, const char* file, uint32_t line);
|
||||
#define MENU_ASSERT(expr) ((expr) ? (void)0U : menu_assert_failed(#expr, __FILE__, __LINE__))
|
||||
#else
|
||||
#define MENU_ASSERT(expr) ((void)0U)
|
||||
#endif
|
||||
|
||||
/* 调试打印定义 -----------------------------------------------------------------------------------------------------*/
|
||||
#if MENU_CONFIG_ENABLE_DEBUG
|
||||
extern void menu_debug_print(const char* fmt, ...);
|
||||
#define MENU_DEBUG(fmt, ...) menu_debug_print(fmt, ##__VA_ARGS__)
|
||||
#else
|
||||
#define MENU_DEBUG(fmt, ...) ((void)0U)
|
||||
#endif
|
||||
|
||||
#endif /* MENU_CONFIG_H */
|
||||
@ -1,778 +0,0 @@
|
||||
/**
|
||||
**********************************************************************************************************************
|
||||
* @file menu_core.c
|
||||
* @brief 菜单组件核心功能实现
|
||||
* @author menu_component
|
||||
* @date 2025-12-19
|
||||
**********************************************************************************************************************
|
||||
*/
|
||||
|
||||
/* Includes ----------------------------------------------------------------------------------------------------------*/
|
||||
#include "menu_types.h"
|
||||
#include "menu_core.h"
|
||||
#include "menu_hash.h"
|
||||
#include "menu_event.h"
|
||||
#include "menu_stack.h"
|
||||
|
||||
#if MENU_CONFIG_ENABLE_PERMISSION
|
||||
#include "menu_permission.h"
|
||||
#endif
|
||||
|
||||
#if MENU_CONFIG_ENABLE_PERSISTENCE
|
||||
#include "menu_persistence.h"
|
||||
#endif
|
||||
|
||||
/* 私有函数原型 -----------------------------------------------------------------------------------------------------*/
|
||||
static MenuErrCode menu_core_init_nodes(MenuCoreCtx* core_ctx);
|
||||
static MenuNode* menu_core_get_free_node(MenuCoreCtx* core_ctx);
|
||||
static MenuErrCode menu_core_state_transition(MenuCoreCtx* core_ctx, MenuEventType event);
|
||||
static MenuErrCode menu_core_navigate_enter(MenuCoreCtx* core_ctx, MenuNodeId node_id);
|
||||
static MenuErrCode menu_core_navigate_back(MenuCoreCtx* core_ctx);
|
||||
static MenuErrCode menu_core_navigate_up(MenuCoreCtx* core_ctx);
|
||||
static MenuErrCode menu_core_navigate_down(MenuCoreCtx* core_ctx);
|
||||
|
||||
/* 全局变量 ---------------------------------------------------------------------------------------------------------*/
|
||||
static MenuCoreCtx sg_menu_core_ctx;
|
||||
|
||||
/* 状态转换规则定义 -------------------------------------------------------------------------------------------------*/
|
||||
#if MENU_CONFIG_ENABLE_STATE_MACHINE_EXT
|
||||
static MenuStateTransition sg_default_transitions[] = {
|
||||
// 初始状态转换
|
||||
{MENU_STATE_INIT, MENU_EVENT_KEY_ENTER, MENU_STATE_NORMAL, NULL, "Init to Normal"},
|
||||
{MENU_STATE_INIT, MENU_EVENT_TIMEOUT, MENU_STATE_ERROR, NULL, "Init timeout to Error"},
|
||||
|
||||
// 正常状态转换
|
||||
{MENU_STATE_NORMAL, MENU_EVENT_KEY_ENTER, MENU_STATE_PARAM_EDIT, NULL, "Normal to Param Edit"},
|
||||
{MENU_STATE_NORMAL, MENU_EVENT_KEY_ESC, MENU_STATE_NORMAL, NULL, "Normal to Normal"},
|
||||
{MENU_STATE_NORMAL, MENU_EVENT_KEY_UP, MENU_STATE_NORMAL, NULL, "Normal to Normal (Up)"},
|
||||
{MENU_STATE_NORMAL, MENU_EVENT_KEY_DOWN, MENU_STATE_NORMAL, NULL, "Normal to Normal (Down)"},
|
||||
|
||||
// 参数编辑状态转换
|
||||
{MENU_STATE_PARAM_EDIT, MENU_EVENT_KEY_ENTER, MENU_STATE_CONFIRM, NULL, "Param Edit to Confirm"},
|
||||
{MENU_STATE_PARAM_EDIT, MENU_EVENT_KEY_ESC, MENU_STATE_NORMAL, NULL, "Param Edit to Normal"},
|
||||
|
||||
// 确认状态转换
|
||||
{MENU_STATE_CONFIRM, MENU_EVENT_KEY_ENTER, MENU_STATE_NORMAL, NULL, "Confirm to Normal"},
|
||||
{MENU_STATE_CONFIRM, MENU_EVENT_KEY_ESC, MENU_STATE_PARAM_EDIT, NULL, "Confirm to Param Edit"},
|
||||
|
||||
// 错误状态转换
|
||||
{MENU_STATE_ERROR, MENU_EVENT_KEY_ESC, MENU_STATE_INIT, NULL, "Error to Init"},
|
||||
};
|
||||
|
||||
#define MENU_DEFAULT_TRANSITION_COUNT (sizeof(sg_default_transitions) / sizeof(MenuStateTransition))
|
||||
#endif
|
||||
|
||||
/* 函数实现 ---------------------------------------------------------------------------------------------------------*/
|
||||
|
||||
/**
|
||||
* @brief 初始化菜单核心
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_core_init(void)
|
||||
{
|
||||
MenuCoreCtx* core_ctx = &sg_menu_core_ctx;
|
||||
|
||||
// 初始化核心上下文
|
||||
core_ctx->is_initialized = false;
|
||||
core_ctx->current_node_id = 0;
|
||||
core_ctx->current_state = MENU_STATE_INIT;
|
||||
core_ctx->last_refresh_tick = 0;
|
||||
core_ctx->error_code = MENU_ERR_OK;
|
||||
core_ctx->error_msg = NULL;
|
||||
core_ctx->node_count = 0;
|
||||
core_ctx->free_node_count = MENU_CONFIG_MAX_NODES;
|
||||
|
||||
#if MENU_CONFIG_ENABLE_STATE_MACHINE_EXT
|
||||
core_ctx->custom_transition_count = 0;
|
||||
#endif
|
||||
|
||||
// 初始化各个组件
|
||||
menu_core_init_nodes(core_ctx);
|
||||
menu_hash_init(core_ctx);
|
||||
menu_stack_init(&core_ctx->stack, MENU_CONFIG_STACK_DEPTH);
|
||||
menu_nav_path_init(&core_ctx->nav_path, MENU_CONFIG_STACK_DEPTH);
|
||||
menu_event_queue_init(&core_ctx->event_queue);
|
||||
|
||||
#if MENU_CONFIG_ENABLE_STATE_MACHINE_EXT
|
||||
// 注册默认状态转换规则
|
||||
for (uint16_t i = 0; i < MENU_DEFAULT_TRANSITION_COUNT; i++) {
|
||||
core_ctx->custom_transitions[i] = sg_default_transitions[i];
|
||||
}
|
||||
core_ctx->custom_transition_count = MENU_DEFAULT_TRANSITION_COUNT;
|
||||
#endif
|
||||
|
||||
#if MENU_CONFIG_ENABLE_PERMISSION
|
||||
// 初始化权限管理模块
|
||||
menu_permission_init();
|
||||
#endif
|
||||
|
||||
#if MENU_CONFIG_ENABLE_PERSISTENCE
|
||||
// 初始化状态持久化模块
|
||||
menu_persistence_init();
|
||||
#endif
|
||||
|
||||
core_ctx->is_initialized = true;
|
||||
return MENU_ERR_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 反初始化菜单核心
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_core_deinit(void)
|
||||
{
|
||||
MenuCoreCtx* core_ctx = &sg_menu_core_ctx;
|
||||
|
||||
if (!core_ctx->is_initialized) {
|
||||
return MENU_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
// 清空事件队列
|
||||
menu_event_queue_clear(&core_ctx->event_queue);
|
||||
|
||||
// 清空栈和导航路径
|
||||
menu_stack_clear(&core_ctx->stack);
|
||||
menu_nav_path_clear(&core_ctx->nav_path);
|
||||
|
||||
// 重置哈希表
|
||||
menu_hash_init(core_ctx);
|
||||
|
||||
// 重置所有节点
|
||||
menu_core_init_nodes(core_ctx);
|
||||
|
||||
// 重置核心上下文
|
||||
core_ctx->is_initialized = false;
|
||||
core_ctx->current_node_id = 0;
|
||||
core_ctx->current_state = MENU_STATE_INIT;
|
||||
core_ctx->last_refresh_tick = 0;
|
||||
core_ctx->error_code = MENU_ERR_OK;
|
||||
core_ctx->error_msg = NULL;
|
||||
core_ctx->node_count = 0;
|
||||
core_ctx->free_node_count = MENU_CONFIG_MAX_NODES;
|
||||
|
||||
#if MENU_CONFIG_ENABLE_STATE_MACHINE_EXT
|
||||
core_ctx->custom_transition_count = 0;
|
||||
#endif
|
||||
|
||||
#if MENU_CONFIG_ENABLE_PERMISSION
|
||||
// 反初始化权限管理模块
|
||||
menu_permission_deinit();
|
||||
#endif
|
||||
|
||||
#if MENU_CONFIG_ENABLE_PERSISTENCE
|
||||
// 反初始化状态持久化模块
|
||||
menu_persistence_deinit();
|
||||
#endif
|
||||
|
||||
return MENU_ERR_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 初始化菜单节点池
|
||||
* @param core_ctx 菜单核心上下文
|
||||
* @return 错误码
|
||||
*/
|
||||
static MenuErrCode menu_core_init_nodes(MenuCoreCtx* core_ctx)
|
||||
{
|
||||
for (uint16_t i = 0; i < MENU_CONFIG_MAX_NODES; i++) {
|
||||
MenuNode* node = &core_ctx->nodes[i];
|
||||
|
||||
node->id = i + 1; // 节点ID从1开始
|
||||
node->parent_id = 0;
|
||||
node->name = NULL;
|
||||
node->enter_cb = NULL;
|
||||
node->exit_cb = NULL;
|
||||
node->first_child_id = 0;
|
||||
node->next_sibling_id = 0;
|
||||
node->prev_sibling_id = 0;
|
||||
node->hash_next_id = 0;
|
||||
|
||||
node->flags.is_registered = false;
|
||||
node->flags.is_selected = false;
|
||||
node->flags.is_visible = true; // 默认可见
|
||||
node->flags.is_enabled = true; // 默认启用
|
||||
|
||||
#if MENU_CONFIG_ENABLE_PARAM
|
||||
node->param_id = 0;
|
||||
#endif
|
||||
|
||||
#if MENU_CONFIG_ENABLE_PERMISSION
|
||||
node->permission_level = 0; // 默认权限级别为0(最低权限)
|
||||
#endif
|
||||
}
|
||||
|
||||
return MENU_ERR_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取一个空闲节点
|
||||
* @param core_ctx 菜单核心上下文
|
||||
* @return 空闲节点指针,没有空闲节点返回NULL
|
||||
*/
|
||||
static MenuNode* menu_core_get_free_node(MenuCoreCtx* core_ctx)
|
||||
{
|
||||
static uint16_t last_allocated_index = 0; // 记录上次分配的索引,实现循环分配
|
||||
|
||||
// 先从上次分配的位置开始查找,实现循环分配
|
||||
for (uint16_t i = 0; i < MENU_CONFIG_MAX_NODES; i++) {
|
||||
uint16_t index = (last_allocated_index + i) % MENU_CONFIG_MAX_NODES;
|
||||
MenuNode* node = &core_ctx->nodes[index];
|
||||
if (!node->flags.is_registered) {
|
||||
last_allocated_index = index;
|
||||
return node;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 注册菜单节点
|
||||
* @param node_id 节点ID(0表示自动分配)
|
||||
* @param parent_id 父节点ID
|
||||
* @param name 节点名称
|
||||
* @param enter_cb 进入回调
|
||||
* @param exit_cb 退出回调
|
||||
* @return 实际分配的节点ID
|
||||
*/
|
||||
MenuNodeId menu_core_register_node(MenuNodeId node_id, MenuNodeId parent_id, const char* name, MenuCallback enter_cb, MenuCallback exit_cb)
|
||||
{
|
||||
MenuCoreCtx* core_ctx = &sg_menu_core_ctx;
|
||||
|
||||
// 参数验证
|
||||
if (!core_ctx->is_initialized) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 检查父节点是否有效(如果不是根节点)
|
||||
if (parent_id != 0) {
|
||||
MenuNode* parent_node = menu_hash_find(core_ctx, parent_id);
|
||||
if (parent_node == NULL || !parent_node->flags.is_registered) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// 检查节点名称是否有效
|
||||
if (name == NULL) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
MenuNode* node = NULL;
|
||||
|
||||
if (node_id == 0) {
|
||||
// 自动分配节点ID
|
||||
node = menu_core_get_free_node(core_ctx);
|
||||
if (node == NULL) {
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
// 使用指定节点ID
|
||||
if (node_id > MENU_CONFIG_MAX_NODES || node_id < 1) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
node = &core_ctx->nodes[node_id - 1];
|
||||
if (node->flags.is_registered) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化节点
|
||||
node->parent_id = parent_id;
|
||||
node->name = name;
|
||||
node->enter_cb = enter_cb;
|
||||
node->exit_cb = exit_cb;
|
||||
|
||||
// 添加到父节点的子节点列表
|
||||
if (parent_id != 0) {
|
||||
MenuNode* parent_node = menu_hash_find(core_ctx, parent_id);
|
||||
if (parent_node != NULL) {
|
||||
if (parent_node->first_child_id == 0) {
|
||||
// 父节点没有子节点,直接添加
|
||||
parent_node->first_child_id = node->id;
|
||||
} else {
|
||||
// 查找父节点的最后一个子节点
|
||||
MenuNode* last_child = menu_hash_find(core_ctx, parent_node->first_child_id);
|
||||
while (last_child->next_sibling_id != 0) {
|
||||
last_child = menu_hash_find(core_ctx, last_child->next_sibling_id);
|
||||
}
|
||||
|
||||
// 添加到最后一个子节点的后面
|
||||
last_child->next_sibling_id = node->id;
|
||||
node->prev_sibling_id = last_child->id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 添加到哈希表
|
||||
menu_hash_add(core_ctx, node);
|
||||
|
||||
// 更新节点计数
|
||||
node->flags.is_registered = true;
|
||||
core_ctx->node_count++;
|
||||
core_ctx->free_node_count--;
|
||||
|
||||
// 如果是第一个节点,设置为当前节点
|
||||
if (core_ctx->current_node_id == 0) {
|
||||
core_ctx->current_node_id = node->id;
|
||||
menu_nav_path_add(&core_ctx->nav_path, node->id);
|
||||
// 初始化栈,将根节点压入栈中,确保栈和导航路径深度一致
|
||||
menu_stack_push(&core_ctx->stack, node->id);
|
||||
}
|
||||
|
||||
return node->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 注销菜单节点
|
||||
* @param node_id 节点ID
|
||||
* @return 错误码
|
||||
*/
|
||||
static MenuErrCode menu_core_unregister_node_recursive(MenuCoreCtx* core_ctx, MenuNodeId node_id);
|
||||
|
||||
/**
|
||||
* @brief 注销菜单节点
|
||||
* @param node_id 节点ID
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_core_unregister_node(MenuNodeId node_id)
|
||||
{
|
||||
MenuCoreCtx* core_ctx = &sg_menu_core_ctx;
|
||||
|
||||
// 参数验证
|
||||
if (!core_ctx->is_initialized) {
|
||||
return MENU_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
if (node_id == 0 || node_id > MENU_CONFIG_MAX_NODES) {
|
||||
return MENU_ERR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
// 递归注销所有子节点
|
||||
return menu_core_unregister_node_recursive(core_ctx, node_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 递归注销菜单节点及其所有子节点
|
||||
* @param core_ctx 菜单核心上下文
|
||||
* @param node_id 节点ID
|
||||
* @return 错误码
|
||||
*/
|
||||
static MenuErrCode menu_core_unregister_node_recursive(MenuCoreCtx* core_ctx, MenuNodeId node_id)
|
||||
{
|
||||
MenuNode* node = menu_hash_find(core_ctx, node_id);
|
||||
if (node == NULL || !node->flags.is_registered) {
|
||||
return MENU_ERR_NODE_NOT_FOUND;
|
||||
}
|
||||
|
||||
// 递归注销所有子节点
|
||||
if (node->first_child_id != 0) {
|
||||
MenuNode* child_node = menu_hash_find(core_ctx, node->first_child_id);
|
||||
while (child_node != NULL) {
|
||||
MenuNodeId next_child_id = child_node->next_sibling_id;
|
||||
// 递归注销子节点
|
||||
menu_core_unregister_node_recursive(core_ctx, child_node->id);
|
||||
child_node = menu_hash_find(core_ctx, next_child_id);
|
||||
}
|
||||
}
|
||||
|
||||
// 从哈希表中移除
|
||||
menu_hash_remove(core_ctx, node);
|
||||
|
||||
// 从父节点的子节点列表中移除
|
||||
if (node->parent_id != 0) {
|
||||
MenuNode* parent_node = menu_hash_find(core_ctx, node->parent_id);
|
||||
if (parent_node != NULL) {
|
||||
if (parent_node->first_child_id == node_id) {
|
||||
// 移除的是第一个子节点
|
||||
parent_node->first_child_id = node->next_sibling_id;
|
||||
} else {
|
||||
// 移除的是中间或最后一个子节点
|
||||
if (node->prev_sibling_id != 0) {
|
||||
MenuNode* prev_sibling = menu_hash_find(core_ctx, node->prev_sibling_id);
|
||||
if (prev_sibling != NULL) {
|
||||
prev_sibling->next_sibling_id = node->next_sibling_id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (node->next_sibling_id != 0) {
|
||||
MenuNode* next_sibling = menu_hash_find(core_ctx, node->next_sibling_id);
|
||||
if (next_sibling != NULL) {
|
||||
next_sibling->prev_sibling_id = node->prev_sibling_id;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 重置节点状态
|
||||
node->flags.is_registered = false;
|
||||
node->flags.is_selected = false;
|
||||
node->parent_id = 0;
|
||||
node->next_sibling_id = 0;
|
||||
node->prev_sibling_id = 0;
|
||||
node->first_child_id = 0;
|
||||
|
||||
// 更新节点计数
|
||||
core_ctx->node_count--;
|
||||
core_ctx->free_node_count++;
|
||||
|
||||
return MENU_ERR_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 状态转换处理
|
||||
* @param core_ctx 菜单核心上下文
|
||||
* @param event 事件类型
|
||||
* @return 错误码
|
||||
*/
|
||||
static MenuErrCode menu_core_state_transition(MenuCoreCtx* core_ctx, MenuEventType event)
|
||||
{
|
||||
MenuStateTransition default_transition = {0};
|
||||
MenuStateTransition* matched_transition = NULL;
|
||||
|
||||
// 1. 查找匹配的自定义状态转换规则
|
||||
#if MENU_CONFIG_ENABLE_STATE_MACHINE_EXT
|
||||
for (uint16_t i = 0; i < core_ctx->custom_transition_count; i++) {
|
||||
MenuStateTransition* transition = &core_ctx->custom_transitions[i];
|
||||
|
||||
if (transition->current_state == core_ctx->current_state && transition->event == event) {
|
||||
matched_transition = transition;
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// 2. 如果没有找到匹配的自定义规则,使用默认状态转换
|
||||
if (matched_transition == NULL) {
|
||||
// 默认状态转换规则:保持当前状态不变
|
||||
default_transition.current_state = core_ctx->current_state;
|
||||
default_transition.event = event;
|
||||
default_transition.next_state = core_ctx->current_state; // 默认不改变状态
|
||||
default_transition.action = NULL;
|
||||
default_transition.desc = "默认状态转换";
|
||||
matched_transition = &default_transition;
|
||||
}
|
||||
|
||||
// 3. 先更新状态,再执行转换动作
|
||||
MenuState old_state = core_ctx->current_state;
|
||||
core_ctx->current_state = matched_transition->next_state;
|
||||
|
||||
// 4. 执行状态转换动作(如果有)
|
||||
if (matched_transition->action != NULL) {
|
||||
MenuErrCode err = matched_transition->action(core_ctx);
|
||||
if (err != MENU_ERR_OK) {
|
||||
// 动作执行失败,恢复原状态
|
||||
core_ctx->current_state = old_state;
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
return MENU_ERR_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 导航进入子菜单
|
||||
* @param core_ctx 菜单核心上下文
|
||||
* @param node_id 节点ID
|
||||
* @return 错误码
|
||||
*/
|
||||
static MenuErrCode menu_core_navigate_enter(MenuCoreCtx* core_ctx, MenuNodeId node_id)
|
||||
{
|
||||
MenuNode* node = menu_hash_find(core_ctx, node_id);
|
||||
if (node == NULL) {
|
||||
return MENU_ERR_NODE_NOT_FOUND;
|
||||
}
|
||||
|
||||
// 如果当前节点没有子节点,直接返回
|
||||
if (node->first_child_id == 0) {
|
||||
return MENU_ERR_OK;
|
||||
}
|
||||
|
||||
// 进入第一个子节点
|
||||
MenuNodeId target_node_id = node->first_child_id;
|
||||
MenuNode* target_node = menu_hash_find(core_ctx, target_node_id);
|
||||
if (target_node == NULL) {
|
||||
return MENU_ERR_NODE_NOT_FOUND;
|
||||
}
|
||||
|
||||
// 执行当前节点的退出回调
|
||||
MenuNode* curr_node = menu_hash_find(core_ctx, core_ctx->current_node_id);
|
||||
if (curr_node != NULL && curr_node->exit_cb != NULL) {
|
||||
curr_node->exit_cb(core_ctx->current_node_id, core_ctx);
|
||||
}
|
||||
|
||||
// 压入当前节点到栈中
|
||||
MenuErrCode err = menu_stack_push(&core_ctx->stack, core_ctx->current_node_id);
|
||||
if (err != MENU_ERR_OK) {
|
||||
return err;
|
||||
}
|
||||
|
||||
// 更新当前节点
|
||||
core_ctx->current_node_id = target_node_id;
|
||||
|
||||
// 添加到导航路径
|
||||
err = menu_nav_path_add(&core_ctx->nav_path, target_node_id);
|
||||
if (err != MENU_ERR_OK) {
|
||||
menu_stack_pop(&core_ctx->stack, &node_id);
|
||||
core_ctx->current_node_id = node_id;
|
||||
return err;
|
||||
}
|
||||
|
||||
// 执行新节点的进入回调
|
||||
if (target_node->enter_cb != NULL) {
|
||||
target_node->enter_cb(target_node_id, core_ctx);
|
||||
}
|
||||
|
||||
return MENU_ERR_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 导航返回上一级菜单
|
||||
* @param core_ctx 菜单核心上下文
|
||||
* @return 错误码
|
||||
*/
|
||||
static MenuErrCode menu_core_navigate_back(MenuCoreCtx* core_ctx)
|
||||
{
|
||||
// 执行当前节点的退出回调
|
||||
MenuNode* curr_node = menu_hash_find(core_ctx, core_ctx->current_node_id);
|
||||
if (curr_node != NULL && curr_node->exit_cb != NULL) {
|
||||
curr_node->exit_cb(core_ctx->current_node_id, core_ctx);
|
||||
}
|
||||
|
||||
MenuNodeId prev_node_id;
|
||||
MenuErrCode err;
|
||||
|
||||
// 如果栈不为空,弹出上一级节点ID
|
||||
if (!menu_stack_is_empty(&core_ctx->stack)) {
|
||||
err = menu_stack_pop(&core_ctx->stack, &prev_node_id);
|
||||
if (err != MENU_ERR_OK) {
|
||||
return err;
|
||||
}
|
||||
} else {
|
||||
// 栈为空,返回到根节点
|
||||
// 查找根节点
|
||||
prev_node_id = 0;
|
||||
for (uint16_t i = 0; i < MENU_CONFIG_MAX_NODES; i++) {
|
||||
MenuNode* node = &core_ctx->nodes[i];
|
||||
if (node->flags.is_registered && node->parent_id == 0) {
|
||||
prev_node_id = node->id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没有找到根节点,返回错误
|
||||
if (prev_node_id == 0) {
|
||||
return MENU_ERR_NODE_NOT_FOUND;
|
||||
}
|
||||
}
|
||||
|
||||
// 从导航路径移除当前节点
|
||||
err = menu_nav_path_remove_last(&core_ctx->nav_path);
|
||||
if (err != MENU_ERR_OK) {
|
||||
// 如果导航路径移除失败,检查是否还有深度
|
||||
if (core_ctx->nav_path.depth > 0) {
|
||||
// 导航路径还有深度,直接更新当前节点
|
||||
core_ctx->current_node_id = prev_node_id;
|
||||
} else {
|
||||
// 导航路径已为空,添加根节点
|
||||
menu_nav_path_add(&core_ctx->nav_path, prev_node_id);
|
||||
}
|
||||
} else {
|
||||
// 导航路径移除成功,更新当前节点
|
||||
core_ctx->current_node_id = prev_node_id;
|
||||
}
|
||||
|
||||
// 执行上一级节点的进入回调
|
||||
MenuNode* prev_node = menu_hash_find(core_ctx, prev_node_id);
|
||||
if (prev_node != NULL && prev_node->enter_cb != NULL) {
|
||||
prev_node->enter_cb(prev_node_id, core_ctx);
|
||||
}
|
||||
|
||||
return MENU_ERR_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 导航向上选择
|
||||
* @param core_ctx 菜单核心上下文
|
||||
* @return 错误码
|
||||
*/
|
||||
static MenuErrCode menu_core_navigate_up(MenuCoreCtx* core_ctx)
|
||||
{
|
||||
MenuNode* curr_node = menu_hash_find(core_ctx, core_ctx->current_node_id);
|
||||
if (curr_node == NULL) {
|
||||
return MENU_ERR_NODE_NOT_FOUND;
|
||||
}
|
||||
|
||||
// 查找当前节点的父节点
|
||||
MenuNode* parent_node = menu_hash_find(core_ctx, curr_node->parent_id);
|
||||
|
||||
// 如果当前节点有父节点,在同级节点中向上切换
|
||||
if (parent_node != NULL) {
|
||||
// 查找同级的上一个兄弟节点
|
||||
if (curr_node->prev_sibling_id != 0) {
|
||||
core_ctx->current_node_id = curr_node->prev_sibling_id;
|
||||
} else if (parent_node->first_child_id != 0) {
|
||||
// 没有上一个兄弟节点,查找父节点的最后一个子节点(环形导航)
|
||||
MenuNode* last_child = menu_hash_find(core_ctx, parent_node->first_child_id);
|
||||
if (last_child != NULL) {
|
||||
// 查找最后一个子节点
|
||||
while (last_child->next_sibling_id != 0) {
|
||||
MenuNode* next_child = menu_hash_find(core_ctx, last_child->next_sibling_id);
|
||||
if (next_child == NULL) {
|
||||
break; // 防止无限循环,安全退出
|
||||
}
|
||||
last_child = next_child;
|
||||
}
|
||||
|
||||
core_ctx->current_node_id = last_child->id;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 如果当前节点是根节点,在其子节点中向上切换
|
||||
if (curr_node->first_child_id != 0) {
|
||||
// 查找最后一个子节点
|
||||
MenuNode* last_child = menu_hash_find(core_ctx, curr_node->first_child_id);
|
||||
if (last_child != NULL) {
|
||||
while (last_child->next_sibling_id != 0) {
|
||||
MenuNode* next_child = menu_hash_find(core_ctx, last_child->next_sibling_id);
|
||||
if (next_child == NULL) {
|
||||
break; // 防止无限循环,安全退出
|
||||
}
|
||||
last_child = next_child;
|
||||
}
|
||||
|
||||
core_ctx->current_node_id = last_child->id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return MENU_ERR_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 导航向下选择
|
||||
* @param core_ctx 菜单核心上下文
|
||||
* @return 错误码
|
||||
*/
|
||||
static MenuErrCode menu_core_navigate_down(MenuCoreCtx* core_ctx)
|
||||
{
|
||||
MenuNode* curr_node = menu_hash_find(core_ctx, core_ctx->current_node_id);
|
||||
if (curr_node == NULL) {
|
||||
return MENU_ERR_NODE_NOT_FOUND;
|
||||
}
|
||||
|
||||
// 查找当前节点的父节点
|
||||
MenuNode* parent_node = menu_hash_find(core_ctx, curr_node->parent_id);
|
||||
|
||||
// 如果当前节点有父节点,在同级节点中向下切换
|
||||
if (parent_node != NULL) {
|
||||
// 查找同级的下一个兄弟节点
|
||||
if (curr_node->next_sibling_id != 0) {
|
||||
core_ctx->current_node_id = curr_node->next_sibling_id;
|
||||
} else if (parent_node->first_child_id != 0) {
|
||||
// 没有下一个兄弟节点,选择父节点的第一个子节点(环形导航)
|
||||
core_ctx->current_node_id = parent_node->first_child_id;
|
||||
}
|
||||
} else {
|
||||
// 如果当前节点是根节点,在其子节点中向下切换
|
||||
if (curr_node->first_child_id != 0) {
|
||||
// 导航到第一个子节点
|
||||
MenuNode* first_child = menu_hash_find(core_ctx, curr_node->first_child_id);
|
||||
if (first_child != NULL) {
|
||||
core_ctx->current_node_id = first_child->id;
|
||||
}
|
||||
}
|
||||
// 如果根节点没有子节点,保持当前节点不变
|
||||
}
|
||||
|
||||
return MENU_ERR_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 菜单主循环
|
||||
* @param tick 当前系统时间(ms)
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_core_main_loop(uint32_t tick)
|
||||
{
|
||||
MenuCoreCtx* core_ctx = &sg_menu_core_ctx;
|
||||
|
||||
if (!core_ctx->is_initialized) {
|
||||
return MENU_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
// 更新最后刷新时间
|
||||
core_ctx->last_refresh_tick = tick;
|
||||
|
||||
// 处理事件队列
|
||||
while (!menu_event_queue_is_empty(&core_ctx->event_queue)) {
|
||||
MenuEvent event;
|
||||
MenuErrCode err = menu_event_queue_pop(&core_ctx->event_queue, &event);
|
||||
if (err != MENU_ERR_OK) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 处理事件
|
||||
switch (event.type) {
|
||||
case MENU_EVENT_KEY_UP:
|
||||
menu_core_navigate_up(core_ctx);
|
||||
break;
|
||||
case MENU_EVENT_KEY_DOWN:
|
||||
menu_core_navigate_down(core_ctx);
|
||||
break;
|
||||
case MENU_EVENT_KEY_ENTER:
|
||||
menu_core_navigate_enter(core_ctx, core_ctx->current_node_id);
|
||||
break;
|
||||
case MENU_EVENT_KEY_ESC:
|
||||
menu_core_navigate_back(core_ctx);
|
||||
break;
|
||||
default:
|
||||
// 其他事件通过状态机处理
|
||||
menu_core_state_transition(core_ctx, event.type);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 处理自动保存
|
||||
#if MENU_CONFIG_ENABLE_PERSISTENCE
|
||||
menu_persistence_process_auto_save(tick);
|
||||
#endif
|
||||
|
||||
return MENU_ERR_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取菜单核心上下文
|
||||
* @return 菜单核心上下文指针
|
||||
*/
|
||||
MenuCoreCtx* menu_get_core_ctx(void)
|
||||
{
|
||||
return &sg_menu_core_ctx;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取当前选中的节点ID
|
||||
* @return 当前节点ID
|
||||
*/
|
||||
MenuNodeId menu_get_current_node_id(void)
|
||||
{
|
||||
return sg_menu_core_ctx.current_node_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取当前菜单状态
|
||||
* @return 当前菜单状态
|
||||
*/
|
||||
MenuState menu_get_current_state(void)
|
||||
{
|
||||
return sg_menu_core_ctx.current_state;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取导航深度
|
||||
* @return 导航深度
|
||||
*/
|
||||
uint8_t menu_core_get_nav_depth(void)
|
||||
{
|
||||
return sg_menu_core_ctx.nav_path.depth;
|
||||
}
|
||||
@ -1,246 +0,0 @@
|
||||
/**
|
||||
**********************************************************************************************************************
|
||||
* @file menu_core.h
|
||||
* @brief 菜单组件核心功能声明
|
||||
* @author menu_component
|
||||
* @date 2025-12-19
|
||||
**********************************************************************************************************************
|
||||
*/
|
||||
|
||||
#ifndef MENU_CORE_H
|
||||
#define MENU_CORE_H
|
||||
|
||||
/* Includes ----------------------------------------------------------------------------------------------------------*/
|
||||
#include "menu_types.h"
|
||||
|
||||
/* 函数声明 ---------------------------------------------------------------------------------------------------------*/
|
||||
|
||||
/**
|
||||
* @brief 初始化菜单核心
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_core_init(void);
|
||||
|
||||
/**
|
||||
* @brief 反初始化菜单核心
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_core_deinit(void);
|
||||
|
||||
/**
|
||||
* @brief 注册菜单节点
|
||||
* @param node_id 节点ID(0表示自动分配)
|
||||
* @param parent_id 父节点ID
|
||||
* @param name 节点名称
|
||||
* @param enter_cb 进入回调
|
||||
* @param exit_cb 退出回调
|
||||
* @return 实际分配的节点ID
|
||||
*/
|
||||
MenuNodeId menu_core_register_node(MenuNodeId node_id, MenuNodeId parent_id, const char* name, MenuCallback enter_cb, MenuCallback exit_cb);
|
||||
|
||||
/**
|
||||
* @brief 注销菜单节点
|
||||
* @param node_id 节点ID
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_core_unregister_node(MenuNodeId node_id);
|
||||
|
||||
/**
|
||||
* @brief 获取菜单核心上下文
|
||||
* @return 菜单核心上下文指针
|
||||
*/
|
||||
struct MenuCoreCtx* menu_get_core_ctx(void);
|
||||
|
||||
/**
|
||||
* @brief 获取当前选中的节点ID
|
||||
* @return 当前节点ID
|
||||
*/
|
||||
MenuNodeId menu_get_current_node_id(void);
|
||||
|
||||
/**
|
||||
* @brief 获取当前菜单状态
|
||||
* @return 当前菜单状态
|
||||
*/
|
||||
MenuState menu_get_current_state(void);
|
||||
|
||||
/**
|
||||
* @brief 获取导航深度
|
||||
* @return 导航深度
|
||||
*/
|
||||
uint8_t menu_core_get_nav_depth(void);
|
||||
|
||||
/**
|
||||
* @brief 菜单主循环
|
||||
* @param tick 当前系统时间(ms)
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_core_main_loop(uint32_t tick);
|
||||
|
||||
/* 权限管理函数声明 -------------------------------------------------------------------------------------------------*/
|
||||
|
||||
#if MENU_CONFIG_ENABLE_PERMISSION
|
||||
|
||||
/**
|
||||
* @brief 初始化权限管理模块
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_permission_init(void);
|
||||
|
||||
/**
|
||||
* @brief 反初始化权限管理模块
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_permission_deinit(void);
|
||||
|
||||
/**
|
||||
* @brief 注册角色
|
||||
* @param role 角色结构体指针
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_permission_register_role(const MenuRole* role);
|
||||
|
||||
/**
|
||||
* @brief 注销角色
|
||||
* @param role_id 角色ID
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_permission_unregister_role(MenuRoleId role_id);
|
||||
|
||||
/**
|
||||
* @brief 设置当前角色
|
||||
* @param role_id 角色ID
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_permission_set_current_role(MenuRoleId role_id);
|
||||
|
||||
/**
|
||||
* @brief 获取当前角色
|
||||
* @return 当前角色ID
|
||||
*/
|
||||
MenuRoleId menu_permission_get_current_role(void);
|
||||
|
||||
/**
|
||||
* @brief 检查节点是否可访问
|
||||
* @param node_id 节点ID
|
||||
* @return true表示可访问,false表示不可访问
|
||||
*/
|
||||
bool menu_permission_check_node_access(MenuNodeId node_id);
|
||||
|
||||
/**
|
||||
* @brief 更新节点权限级别
|
||||
* @param node_id 节点ID
|
||||
* @param permission_level 权限级别
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_permission_update_node_level(MenuNodeId node_id, MenuPermissionLevel permission_level);
|
||||
|
||||
/**
|
||||
* @brief 获取节点权限级别
|
||||
* @param node_id 节点ID
|
||||
* @return 节点权限级别
|
||||
*/
|
||||
MenuPermissionLevel menu_permission_get_node_level(MenuNodeId node_id);
|
||||
|
||||
/**
|
||||
* @brief 更新所有节点的可见性和可用性
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_permission_update_all_nodes(void);
|
||||
|
||||
/**
|
||||
* @brief 获取角色信息
|
||||
* @param role_id 角色ID
|
||||
* @return 角色结构体指针,NULL表示角色不存在
|
||||
*/
|
||||
const MenuRole* menu_permission_get_role(MenuRoleId role_id);
|
||||
|
||||
#endif /* MENU_CONFIG_ENABLE_PERMISSION */
|
||||
|
||||
/* 状态持久化函数声明 -------------------------------------------------------------------------------------------------*/
|
||||
|
||||
#if MENU_CONFIG_ENABLE_PERSISTENCE
|
||||
|
||||
/**
|
||||
* @brief 初始化状态持久化模块
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_persistence_init(void);
|
||||
|
||||
/**
|
||||
* @brief 反初始化状态持久化模块
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_persistence_deinit(void);
|
||||
|
||||
/**
|
||||
* @brief 注册持久化回调函数
|
||||
* @param save_cb 保存回调函数
|
||||
* @param restore_cb 恢复回调函数
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_persistence_register_callback(MenuPersistenceCallback save_cb, MenuRestoreCallback restore_cb);
|
||||
|
||||
/**
|
||||
* @brief 启用或禁用自动保存
|
||||
* @param enable true表示启用,false表示禁用
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_persistence_set_auto_save(bool enable);
|
||||
|
||||
/**
|
||||
* @brief 获取自动保存状态
|
||||
* @return true表示启用,false表示禁用
|
||||
*/
|
||||
bool menu_persistence_get_auto_save(void);
|
||||
|
||||
/**
|
||||
* @brief 设置自动保存间隔
|
||||
* @param interval 自动保存间隔(ms)
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_persistence_set_auto_save_interval(uint32_t interval);
|
||||
|
||||
/**
|
||||
* @brief 获取自动保存间隔
|
||||
* @return 自动保存间隔(ms)
|
||||
*/
|
||||
uint32_t menu_persistence_get_auto_save_interval(void);
|
||||
|
||||
/**
|
||||
* @brief 手动保存菜单状态
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_persistence_save(void);
|
||||
|
||||
/**
|
||||
* @brief 恢复菜单状态
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_persistence_restore(void);
|
||||
|
||||
/**
|
||||
* @brief 检查持久化数据是否有效
|
||||
* @param data 持久化数据
|
||||
* @param size 数据大小
|
||||
* @return true表示有效,false表示无效
|
||||
*/
|
||||
bool menu_persistence_check_data_valid(const uint8_t* data, uint16_t size);
|
||||
|
||||
/**
|
||||
* @brief 处理自动保存(由菜单主循环调用)
|
||||
* @param tick 当前系统时间(ms)
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_persistence_process_auto_save(uint32_t tick);
|
||||
|
||||
/**
|
||||
* @brief 计算数据校验和
|
||||
* @param data 数据指针
|
||||
* @param size 数据大小
|
||||
* @return 校验和
|
||||
*/
|
||||
uint8_t menu_persistence_calculate_checksum(const uint8_t* data, uint16_t size);
|
||||
|
||||
#endif /* MENU_CONFIG_ENABLE_PERSISTENCE */
|
||||
|
||||
#endif /* MENU_CORE_H */
|
||||
@ -1,186 +0,0 @@
|
||||
/**
|
||||
**********************************************************************************************************************
|
||||
* @file menu_event.c
|
||||
* @brief 菜单组件事件队列实现
|
||||
* @author menu_component
|
||||
* @date 2025-12-19
|
||||
**********************************************************************************************************************
|
||||
*/
|
||||
|
||||
/* Includes ----------------------------------------------------------------------------------------------------------*/
|
||||
#include "menu_types.h"
|
||||
|
||||
/* 函数实现 ---------------------------------------------------------------------------------------------------------*/
|
||||
|
||||
/**
|
||||
* @brief 初始化事件队列
|
||||
* @param queue 事件队列指针
|
||||
*/
|
||||
void menu_event_queue_init(MenuEventQueue* queue)
|
||||
{
|
||||
MENU_ASSERT(queue != NULL);
|
||||
|
||||
queue->head = 0;
|
||||
queue->tail = 0;
|
||||
queue->count = 0;
|
||||
queue->reserved = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 检查事件队列是否为空
|
||||
* @param queue 事件队列指针
|
||||
* @return true为空,false不为空
|
||||
*/
|
||||
bool menu_event_queue_is_empty(const MenuEventQueue* queue)
|
||||
{
|
||||
MENU_ASSERT(queue != NULL);
|
||||
return (queue->count == 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 检查事件队列是否已满
|
||||
* @param queue 事件队列指针
|
||||
* @return true已满,false未满
|
||||
*/
|
||||
bool menu_event_queue_is_full(const MenuEventQueue* queue)
|
||||
{
|
||||
MENU_ASSERT(queue != NULL);
|
||||
return (queue->count >= MENU_CONFIG_EVENT_QUEUE_LEN);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取事件队列中的元素数量
|
||||
* @param queue 事件队列指针
|
||||
* @return 元素数量
|
||||
*/
|
||||
uint8_t menu_event_queue_get_count(const MenuEventQueue* queue)
|
||||
{
|
||||
MENU_ASSERT(queue != NULL);
|
||||
return queue->count;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 向事件队列添加事件
|
||||
* @param queue 事件队列指针
|
||||
* @param event 要添加的事件
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_event_queue_push(MenuEventQueue* queue, const MenuEvent* event)
|
||||
{
|
||||
MENU_ASSERT(queue != NULL);
|
||||
MENU_ASSERT(event != NULL);
|
||||
|
||||
// 检查事件优先级是否合法
|
||||
if (event->priority >= MENU_CONFIG_EVENT_MAX_PRIORITY) {
|
||||
return MENU_ERR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
if (menu_event_queue_is_full(queue)) {
|
||||
// 队列已满,替换低优先级事件
|
||||
uint8_t lowest_prio = 0;
|
||||
uint8_t lowest_idx = queue->head;
|
||||
|
||||
// 找到最低优先级的事件
|
||||
for (uint8_t i = 0; i < MENU_CONFIG_EVENT_QUEUE_LEN; i++) {
|
||||
uint8_t idx = (queue->head + i) % MENU_CONFIG_EVENT_QUEUE_LEN;
|
||||
if (queue->buffer[idx].priority > lowest_prio) {
|
||||
lowest_prio = queue->buffer[idx].priority;
|
||||
lowest_idx = idx;
|
||||
}
|
||||
}
|
||||
|
||||
// 替换最低优先级事件
|
||||
queue->buffer[lowest_idx] = *event;
|
||||
return MENU_ERR_OK;
|
||||
}
|
||||
|
||||
// 队列未满,添加到队尾
|
||||
queue->buffer[queue->tail] = *event;
|
||||
queue->tail = (queue->tail + 1) % MENU_CONFIG_EVENT_QUEUE_LEN;
|
||||
queue->count++;
|
||||
|
||||
return MENU_ERR_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 从事件队列获取下一个事件
|
||||
* @param queue 事件队列指针
|
||||
* @param event 用于存储获取的事件
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_event_queue_pop(MenuEventQueue* queue, MenuEvent* event)
|
||||
{
|
||||
MENU_ASSERT(queue != NULL);
|
||||
MENU_ASSERT(event != NULL);
|
||||
|
||||
if (menu_event_queue_is_empty(queue)) {
|
||||
return MENU_ERR_QUEUE_EMPTY;
|
||||
}
|
||||
|
||||
// 寻找最高优先级的事件
|
||||
uint8_t highest_prio = MENU_CONFIG_EVENT_MAX_PRIORITY;
|
||||
uint8_t highest_idx = queue->head;
|
||||
|
||||
for (uint8_t i = 0; i < queue->count; i++) {
|
||||
uint8_t idx = (queue->head + i) % MENU_CONFIG_EVENT_QUEUE_LEN;
|
||||
if (queue->buffer[idx].priority < highest_prio) {
|
||||
highest_prio = queue->buffer[idx].priority;
|
||||
highest_idx = idx;
|
||||
}
|
||||
}
|
||||
|
||||
// 取出最高优先级事件
|
||||
*event = queue->buffer[highest_idx];
|
||||
|
||||
// 移除该事件,将后续事件前移
|
||||
for (uint8_t i = highest_idx; i != queue->tail; i = (i + 1) % MENU_CONFIG_EVENT_QUEUE_LEN) {
|
||||
uint8_t next_idx = (i + 1) % MENU_CONFIG_EVENT_QUEUE_LEN;
|
||||
if (next_idx == queue->tail) {
|
||||
break;
|
||||
}
|
||||
queue->buffer[i] = queue->buffer[next_idx];
|
||||
}
|
||||
|
||||
// 更新队列状态
|
||||
if (queue->tail > 0) {
|
||||
queue->tail--;
|
||||
} else {
|
||||
queue->tail = MENU_CONFIG_EVENT_QUEUE_LEN - 1;
|
||||
}
|
||||
queue->count--;
|
||||
|
||||
return MENU_ERR_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 清空事件队列
|
||||
* @param queue 事件队列指针
|
||||
*/
|
||||
void menu_event_queue_clear(MenuEventQueue* queue)
|
||||
{
|
||||
MENU_ASSERT(queue != NULL);
|
||||
|
||||
queue->head = 0;
|
||||
queue->tail = 0;
|
||||
queue->count = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 向事件队列推送一个简单事件
|
||||
* @param core_ctx 菜单核心上下文
|
||||
* @param type 事件类型
|
||||
* @param priority 事件优先级
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_core_post_event(struct MenuCoreCtx* core_ctx, MenuEventType type, uint8_t priority)
|
||||
{
|
||||
MenuEvent event;
|
||||
|
||||
event.type = type;
|
||||
event.priority = priority;
|
||||
event.target_node_id = core_ctx->current_node_id;
|
||||
event.data = NULL;
|
||||
event.timestamp = core_ctx->last_refresh_tick;
|
||||
|
||||
return menu_event_queue_push(&core_ctx->event_queue, &event);
|
||||
}
|
||||
@ -1,76 +0,0 @@
|
||||
/**
|
||||
**********************************************************************************************************************
|
||||
* @file menu_event.h
|
||||
* @brief 菜单组件事件队列功能声明
|
||||
* @author menu_component
|
||||
* @date 2025-12-19
|
||||
**********************************************************************************************************************
|
||||
*/
|
||||
|
||||
#ifndef MENU_EVENT_H
|
||||
#define MENU_EVENT_H
|
||||
|
||||
/* Includes ----------------------------------------------------------------------------------------------------------*/
|
||||
#include "menu_types.h"
|
||||
|
||||
/* 函数声明 ---------------------------------------------------------------------------------------------------------*/
|
||||
|
||||
/**
|
||||
* @brief 初始化事件队列
|
||||
* @param queue 事件队列指针
|
||||
*/
|
||||
void menu_event_queue_init(MenuEventQueue* queue);
|
||||
|
||||
/**
|
||||
* @brief 检查事件队列是否为空
|
||||
* @param queue 事件队列指针
|
||||
* @return true为空,false不为空
|
||||
*/
|
||||
bool menu_event_queue_is_empty(const MenuEventQueue* queue);
|
||||
|
||||
/**
|
||||
* @brief 检查事件队列是否已满
|
||||
* @param queue 事件队列指针
|
||||
* @return true已满,false未满
|
||||
*/
|
||||
bool menu_event_queue_is_full(const MenuEventQueue* queue);
|
||||
|
||||
/**
|
||||
* @brief 获取事件队列中的元素数量
|
||||
* @param queue 事件队列指针
|
||||
* @return 元素数量
|
||||
*/
|
||||
uint8_t menu_event_queue_get_count(const MenuEventQueue* queue);
|
||||
|
||||
/**
|
||||
* @brief 向事件队列添加事件
|
||||
* @param queue 事件队列指针
|
||||
* @param event 要添加的事件
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_event_queue_push(MenuEventQueue* queue, const MenuEvent* event);
|
||||
|
||||
/**
|
||||
* @brief 从事件队列获取下一个事件
|
||||
* @param queue 事件队列指针
|
||||
* @param event 用于存储获取的事件
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_event_queue_pop(MenuEventQueue* queue, MenuEvent* event);
|
||||
|
||||
/**
|
||||
* @brief 清空事件队列
|
||||
* @param queue 事件队列指针
|
||||
*/
|
||||
void menu_event_queue_clear(MenuEventQueue* queue);
|
||||
|
||||
/**
|
||||
* @brief 向事件队列推送一个简单事件
|
||||
* @param core_ctx 菜单核心上下文
|
||||
* @param type 事件类型
|
||||
* @param priority 事件优先级
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_core_post_event(struct MenuCoreCtx* core_ctx, MenuEventType type, uint8_t priority);
|
||||
|
||||
#endif /* MENU_EVENT_H */
|
||||
@ -1,141 +0,0 @@
|
||||
/**
|
||||
**********************************************************************************************************************
|
||||
* @file menu_hash.c
|
||||
* @brief 菜单组件哈希表实现
|
||||
* @author menu_component
|
||||
* @date 2025-12-19
|
||||
**********************************************************************************************************************
|
||||
*/
|
||||
|
||||
/* Includes ----------------------------------------------------------------------------------------------------------*/
|
||||
#include "menu_types.h"
|
||||
|
||||
/* 私有函数原型 -----------------------------------------------------------------------------------------------------*/
|
||||
static uint16_t menu_hash_func(MenuNodeId node_id);
|
||||
|
||||
/* 函数实现 ---------------------------------------------------------------------------------------------------------*/
|
||||
|
||||
/**
|
||||
* @brief 优化的哈希函数,用于菜单节点ID
|
||||
* @param node_id 节点ID
|
||||
* @return 哈希值
|
||||
*/
|
||||
static uint16_t menu_hash_func(MenuNodeId node_id)
|
||||
{
|
||||
// 高性能哈希函数,针对连续节点ID优化,减少哈希冲突
|
||||
uint32_t hash = node_id;
|
||||
|
||||
// 使用FNV-1a哈希算法,具有良好的分布性和低冲突率
|
||||
hash = (hash ^ 0x811C9DC5) * 0x01000193;
|
||||
hash = (hash ^ (hash >> 16)) * 0x01000193;
|
||||
hash = hash ^ (hash >> 16);
|
||||
|
||||
return (uint16_t)(hash % MENU_CONFIG_HASH_TABLE_SIZE);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 初始化哈希表
|
||||
* @param core_ctx 菜单核心上下文
|
||||
*/
|
||||
void menu_hash_init(struct MenuCoreCtx* core_ctx)
|
||||
{
|
||||
MENU_ASSERT(core_ctx != NULL);
|
||||
|
||||
// 初始化哈希表所有条目为0
|
||||
for (uint16_t i = 0; i < MENU_CONFIG_HASH_TABLE_SIZE; i++) {
|
||||
core_ctx->hash_table[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 将节点添加到哈希表
|
||||
* @param core_ctx 菜单核心上下文
|
||||
* @param node 要添加的节点
|
||||
*/
|
||||
void menu_hash_add(struct MenuCoreCtx* core_ctx, MenuNode* node)
|
||||
{
|
||||
MENU_ASSERT(core_ctx != NULL);
|
||||
MENU_ASSERT(node != NULL);
|
||||
|
||||
// 计算哈希值
|
||||
uint16_t hash = menu_hash_func(node->id);
|
||||
|
||||
// 将节点插入到哈希表对应位置的链表头部
|
||||
node->hash_next_id = core_ctx->hash_table[hash];
|
||||
core_ctx->hash_table[hash] = node->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 从哈希表中移除节点
|
||||
* @param core_ctx 菜单核心上下文
|
||||
* @param node 要移除的节点
|
||||
*/
|
||||
void menu_hash_remove(struct MenuCoreCtx* core_ctx, MenuNode* node)
|
||||
{
|
||||
MENU_ASSERT(core_ctx != NULL);
|
||||
MENU_ASSERT(node != NULL);
|
||||
|
||||
// 计算哈希值
|
||||
uint16_t hash = menu_hash_func(node->id);
|
||||
|
||||
// 在链表中查找并移除节点
|
||||
MenuNodeId prev_id = 0;
|
||||
MenuNodeId curr_id = core_ctx->hash_table[hash];
|
||||
MenuNode* curr_node = NULL;
|
||||
|
||||
while (curr_id != 0) {
|
||||
curr_node = &core_ctx->nodes[curr_id - 1];
|
||||
|
||||
if (curr_node->id == node->id) {
|
||||
// 找到了节点,进行移除操作
|
||||
if (prev_id == 0) {
|
||||
// 移除的是链表头
|
||||
core_ctx->hash_table[hash] = node->hash_next_id;
|
||||
} else {
|
||||
// 移除的是链表中间节点
|
||||
MenuNode* prev_node = &core_ctx->nodes[prev_id - 1];
|
||||
prev_node->hash_next_id = node->hash_next_id;
|
||||
}
|
||||
|
||||
// 清空节点的哈希表链接
|
||||
node->hash_next_id = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
// 继续遍历
|
||||
prev_id = curr_id;
|
||||
curr_id = curr_node->hash_next_id;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 根据ID查找节点
|
||||
* @param core_ctx 菜单核心上下文
|
||||
* @param node_id 节点ID
|
||||
* @return 找到的节点指针,未找到返回NULL
|
||||
*/
|
||||
MenuNode* menu_hash_find(struct MenuCoreCtx* core_ctx, MenuNodeId node_id)
|
||||
{
|
||||
MENU_ASSERT(core_ctx != NULL);
|
||||
|
||||
// 计算哈希值
|
||||
uint16_t hash = menu_hash_func(node_id);
|
||||
|
||||
// 在链表中查找节点
|
||||
MenuNodeId curr_id = core_ctx->hash_table[hash];
|
||||
|
||||
while (curr_id != 0) {
|
||||
MenuNode* curr_node = &core_ctx->nodes[curr_id - 1];
|
||||
|
||||
if (curr_node->id == node_id) {
|
||||
// 找到了节点
|
||||
return curr_node;
|
||||
}
|
||||
|
||||
// 继续遍历
|
||||
curr_id = curr_node->hash_next_id;
|
||||
}
|
||||
|
||||
// 未找到节点
|
||||
return NULL;
|
||||
}
|
||||
@ -1,46 +0,0 @@
|
||||
/**
|
||||
**********************************************************************************************************************
|
||||
* @file menu_hash.h
|
||||
* @brief 菜单组件哈希表功能声明
|
||||
* @author menu_component
|
||||
* @date 2025-12-19
|
||||
**********************************************************************************************************************
|
||||
*/
|
||||
|
||||
#ifndef MENU_HASH_H
|
||||
#define MENU_HASH_H
|
||||
|
||||
/* Includes ----------------------------------------------------------------------------------------------------------*/
|
||||
#include "menu_types.h"
|
||||
|
||||
/* 函数声明 ---------------------------------------------------------------------------------------------------------*/
|
||||
|
||||
/**
|
||||
* @brief 初始化哈希表
|
||||
* @param core_ctx 菜单核心上下文
|
||||
*/
|
||||
void menu_hash_init(struct MenuCoreCtx* core_ctx);
|
||||
|
||||
/**
|
||||
* @brief 将节点添加到哈希表
|
||||
* @param core_ctx 菜单核心上下文
|
||||
* @param node 要添加的节点
|
||||
*/
|
||||
void menu_hash_add(struct MenuCoreCtx* core_ctx, MenuNode* node);
|
||||
|
||||
/**
|
||||
* @brief 从哈希表中移除节点
|
||||
* @param core_ctx 菜单核心上下文
|
||||
* @param node 要移除的节点
|
||||
*/
|
||||
void menu_hash_remove(struct MenuCoreCtx* core_ctx, MenuNode* node);
|
||||
|
||||
/**
|
||||
* @brief 根据ID查找节点
|
||||
* @param core_ctx 菜单核心上下文
|
||||
* @param node_id 节点ID
|
||||
* @return 找到的节点指针,未找到返回NULL
|
||||
*/
|
||||
MenuNode* menu_hash_find(struct MenuCoreCtx* core_ctx, MenuNodeId node_id);
|
||||
|
||||
#endif /* MENU_HASH_H */
|
||||
@ -1,350 +0,0 @@
|
||||
/**
|
||||
**********************************************************************************************************************
|
||||
* @file menu_permission.c
|
||||
* @brief 菜单组件权限管理功能实现
|
||||
* @author menu_component
|
||||
* @date 2025-12-19
|
||||
**********************************************************************************************************************
|
||||
*/
|
||||
|
||||
/* Includes ----------------------------------------------------------------------------------------------------------*/
|
||||
#include "menu_permission.h"
|
||||
#include "menu_hash.h"
|
||||
|
||||
/* 私有函数原型 -----------------------------------------------------------------------------------------------------*/
|
||||
|
||||
#if MENU_CONFIG_ENABLE_PERMISSION
|
||||
|
||||
/**
|
||||
* @brief 获取角色在角色列表中的索引
|
||||
* @param core_ctx 菜单核心上下文
|
||||
* @param role_id 角色ID
|
||||
* @return 角色索引,-1表示未找到
|
||||
*/
|
||||
static int menu_permission_get_role_index(MenuCoreCtx* core_ctx, MenuRoleId role_id);
|
||||
|
||||
/**
|
||||
* @brief 更新单个节点的可见性和可用性
|
||||
* @param core_ctx 菜单核心上下文
|
||||
* @param node 节点指针
|
||||
* @return 错误码
|
||||
*/
|
||||
static MenuErrCode menu_permission_update_node(MenuCoreCtx* core_ctx, MenuNode* node);
|
||||
|
||||
/* 函数实现 ---------------------------------------------------------------------------------------------------------*/
|
||||
|
||||
/**
|
||||
* @brief 初始化权限管理模块
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_permission_init(void)
|
||||
{
|
||||
MenuCoreCtx* core_ctx = menu_get_core_ctx();
|
||||
|
||||
if (core_ctx == NULL) {
|
||||
return MENU_ERR_NOT_INITIALIZED;
|
||||
}
|
||||
|
||||
// 初始化权限配置
|
||||
core_ctx->permission_config.current_role = 0;
|
||||
core_ctx->permission_config.role_count = 0;
|
||||
|
||||
// 注册默认角色(管理员角色,权限级别最高)
|
||||
MenuRole default_role = {
|
||||
.id = 0,
|
||||
.name = "Admin",
|
||||
.max_level = MENU_CONFIG_PERMISSION_MAX_LEVEL - 1
|
||||
};
|
||||
|
||||
return menu_permission_register_role(&default_role);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 反初始化权限管理模块
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_permission_deinit(void)
|
||||
{
|
||||
MenuCoreCtx* core_ctx = menu_get_core_ctx();
|
||||
|
||||
if (core_ctx == NULL) {
|
||||
return MENU_ERR_NOT_INITIALIZED;
|
||||
}
|
||||
|
||||
// 清空角色列表
|
||||
core_ctx->permission_config.role_count = 0;
|
||||
core_ctx->permission_config.current_role = 0;
|
||||
|
||||
return MENU_ERR_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 注册角色
|
||||
* @param role 角色结构体指针
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_permission_register_role(const MenuRole* role)
|
||||
{
|
||||
MenuCoreCtx* core_ctx = menu_get_core_ctx();
|
||||
|
||||
if (core_ctx == NULL || role == NULL) {
|
||||
return MENU_ERR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
if (core_ctx->permission_config.role_count >= MENU_CONFIG_PERMISSION_MAX_ROLES) {
|
||||
return MENU_ERR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
// 检查角色ID是否已存在
|
||||
if (menu_permission_get_role_index(core_ctx, role->id) >= 0) {
|
||||
return MENU_ERR_ALREADY_EXISTS;
|
||||
}
|
||||
|
||||
// 添加角色到列表
|
||||
core_ctx->permission_config.roles[core_ctx->permission_config.role_count] = *role;
|
||||
core_ctx->permission_config.role_count++;
|
||||
|
||||
// 更新所有节点的可见性和可用性
|
||||
return menu_permission_update_all_nodes();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 注销角色
|
||||
* @param role_id 角色ID
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_permission_unregister_role(MenuRoleId role_id)
|
||||
{
|
||||
MenuCoreCtx* core_ctx = menu_get_core_ctx();
|
||||
int index = menu_permission_get_role_index(core_ctx, role_id);
|
||||
|
||||
if (core_ctx == NULL) {
|
||||
return MENU_ERR_NOT_INITIALIZED;
|
||||
}
|
||||
|
||||
if (index < 0) {
|
||||
return MENU_ERR_NODE_NOT_FOUND;
|
||||
}
|
||||
|
||||
// 如果当前角色被注销,切换到默认角色
|
||||
if (core_ctx->permission_config.current_role == role_id) {
|
||||
core_ctx->permission_config.current_role = 0;
|
||||
}
|
||||
|
||||
// 移除角色(将后续角色前移)
|
||||
for (uint8_t i = index; i < core_ctx->permission_config.role_count - 1; i++) {
|
||||
core_ctx->permission_config.roles[i] = core_ctx->permission_config.roles[i + 1];
|
||||
}
|
||||
core_ctx->permission_config.role_count--;
|
||||
|
||||
// 更新所有节点的可见性和可用性
|
||||
return menu_permission_update_all_nodes();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 设置当前角色
|
||||
* @param role_id 角色ID
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_permission_set_current_role(MenuRoleId role_id)
|
||||
{
|
||||
MenuCoreCtx* core_ctx = menu_get_core_ctx();
|
||||
|
||||
if (core_ctx == NULL) {
|
||||
return MENU_ERR_NOT_INITIALIZED;
|
||||
}
|
||||
|
||||
// 检查角色是否存在
|
||||
if (menu_permission_get_role_index(core_ctx, role_id) < 0) {
|
||||
return MENU_ERR_NODE_NOT_FOUND;
|
||||
}
|
||||
|
||||
// 设置当前角色
|
||||
core_ctx->permission_config.current_role = role_id;
|
||||
|
||||
// 更新所有节点的可见性和可用性
|
||||
return menu_permission_update_all_nodes();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取当前角色
|
||||
* @return 当前角色ID
|
||||
*/
|
||||
MenuRoleId menu_permission_get_current_role(void)
|
||||
{
|
||||
MenuCoreCtx* core_ctx = menu_get_core_ctx();
|
||||
|
||||
if (core_ctx == NULL) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return core_ctx->permission_config.current_role;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 检查节点是否可访问
|
||||
* @param node_id 节点ID
|
||||
* @return true表示可访问,false表示不可访问
|
||||
*/
|
||||
bool menu_permission_check_node_access(MenuNodeId node_id)
|
||||
{
|
||||
MenuCoreCtx* core_ctx = menu_get_core_ctx();
|
||||
MenuNode* node = menu_hash_find(core_ctx, node_id);
|
||||
int role_index;
|
||||
|
||||
if (core_ctx == NULL || node == NULL || !node->flags.is_registered) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 获取当前角色索引
|
||||
role_index = menu_permission_get_role_index(core_ctx, core_ctx->permission_config.current_role);
|
||||
if (role_index < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查权限级别
|
||||
return (node->permission_level <= core_ctx->permission_config.roles[role_index].max_level);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 更新节点权限级别
|
||||
* @param node_id 节点ID
|
||||
* @param permission_level 权限级别
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_permission_update_node_level(MenuNodeId node_id, MenuPermissionLevel permission_level)
|
||||
{
|
||||
MenuCoreCtx* core_ctx = menu_get_core_ctx();
|
||||
MenuNode* node = menu_hash_find(core_ctx, node_id);
|
||||
|
||||
if (core_ctx == NULL || node == NULL || !node->flags.is_registered) {
|
||||
return MENU_ERR_NODE_NOT_FOUND;
|
||||
}
|
||||
|
||||
// 更新权限级别
|
||||
node->permission_level = permission_level;
|
||||
|
||||
// 更新节点的可见性和可用性
|
||||
return menu_permission_update_node(core_ctx, node);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取节点权限级别
|
||||
* @param node_id 节点ID
|
||||
* @return 节点权限级别
|
||||
*/
|
||||
MenuPermissionLevel menu_permission_get_node_level(MenuNodeId node_id)
|
||||
{
|
||||
MenuCoreCtx* core_ctx = menu_get_core_ctx();
|
||||
MenuNode* node = menu_hash_find(core_ctx, node_id);
|
||||
|
||||
if (core_ctx == NULL || node == NULL || !node->flags.is_registered) {
|
||||
return MENU_CONFIG_PERMISSION_MAX_LEVEL;
|
||||
}
|
||||
|
||||
return node->permission_level;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 更新所有节点的可见性和可用性
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_permission_update_all_nodes(void)
|
||||
{
|
||||
MenuCoreCtx* core_ctx = menu_get_core_ctx();
|
||||
|
||||
if (core_ctx == NULL) {
|
||||
return MENU_ERR_NOT_INITIALIZED;
|
||||
}
|
||||
|
||||
// 遍历所有节点,更新可见性和可用性
|
||||
for (uint16_t i = 0; i < MENU_CONFIG_MAX_NODES; i++) {
|
||||
if (core_ctx->nodes[i].flags.is_registered) {
|
||||
menu_permission_update_node(core_ctx, &core_ctx->nodes[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return MENU_ERR_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取角色信息
|
||||
* @param role_id 角色ID
|
||||
* @return 角色结构体指针,NULL表示角色不存在
|
||||
*/
|
||||
const MenuRole* menu_permission_get_role(MenuRoleId role_id)
|
||||
{
|
||||
MenuCoreCtx* core_ctx = menu_get_core_ctx();
|
||||
int index;
|
||||
|
||||
if (core_ctx == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
index = menu_permission_get_role_index(core_ctx, role_id);
|
||||
if (index < 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return &core_ctx->permission_config.roles[index];
|
||||
}
|
||||
|
||||
/* 私有函数实现 -----------------------------------------------------------------------------------------------------*/
|
||||
|
||||
/**
|
||||
* @brief 获取角色在角色列表中的索引
|
||||
* @param core_ctx 菜单核心上下文
|
||||
* @param role_id 角色ID
|
||||
* @return 角色索引,-1表示未找到
|
||||
*/
|
||||
static int menu_permission_get_role_index(MenuCoreCtx* core_ctx, MenuRoleId role_id)
|
||||
{
|
||||
if (core_ctx == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// 遍历角色列表,查找匹配的角色ID
|
||||
for (uint8_t i = 0; i < core_ctx->permission_config.role_count; i++) {
|
||||
if (core_ctx->permission_config.roles[i].id == role_id) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 更新单个节点的可见性和可用性
|
||||
* @param core_ctx 菜单核心上下文
|
||||
* @param node 节点指针
|
||||
* @return 错误码
|
||||
*/
|
||||
static MenuErrCode menu_permission_update_node(MenuCoreCtx* core_ctx, MenuNode* node)
|
||||
{
|
||||
int role_index;
|
||||
bool is_accessible;
|
||||
|
||||
if (core_ctx == NULL || node == NULL) {
|
||||
return MENU_ERR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
// 获取当前角色索引
|
||||
role_index = menu_permission_get_role_index(core_ctx, core_ctx->permission_config.current_role);
|
||||
if (role_index < 0) {
|
||||
// 角色不存在,禁用节点
|
||||
node->flags.is_visible = false;
|
||||
node->flags.is_enabled = false;
|
||||
return MENU_ERR_OK;
|
||||
}
|
||||
|
||||
// 检查节点是否可访问
|
||||
is_accessible = (node->permission_level <= core_ctx->permission_config.roles[role_index].max_level);
|
||||
|
||||
// 更新节点的可见性和可用性
|
||||
node->flags.is_visible = is_accessible;
|
||||
node->flags.is_enabled = is_accessible;
|
||||
|
||||
return MENU_ERR_OK;
|
||||
}
|
||||
|
||||
#endif /* MENU_CONFIG_ENABLE_PERMISSION */
|
||||
@ -1,96 +0,0 @@
|
||||
/**
|
||||
**********************************************************************************************************************
|
||||
* @file menu_permission.h
|
||||
* @brief 菜单组件权限管理功能声明
|
||||
* @author menu_component
|
||||
* @date 2025-12-19
|
||||
**********************************************************************************************************************
|
||||
*/
|
||||
|
||||
#ifndef MENU_PERMISSION_H
|
||||
#define MENU_PERMISSION_H
|
||||
|
||||
/* Includes ----------------------------------------------------------------------------------------------------------*/
|
||||
#include "menu_types.h"
|
||||
|
||||
/* 函数声明 ---------------------------------------------------------------------------------------------------------*/
|
||||
|
||||
#if MENU_CONFIG_ENABLE_PERMISSION
|
||||
|
||||
/**
|
||||
* @brief 初始化权限管理模块
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_permission_init(void);
|
||||
|
||||
/**
|
||||
* @brief 反初始化权限管理模块
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_permission_deinit(void);
|
||||
|
||||
/**
|
||||
* @brief 注册角色
|
||||
* @param role 角色结构体指针
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_permission_register_role(const MenuRole* role);
|
||||
|
||||
/**
|
||||
* @brief 注销角色
|
||||
* @param role_id 角色ID
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_permission_unregister_role(MenuRoleId role_id);
|
||||
|
||||
/**
|
||||
* @brief 设置当前角色
|
||||
* @param role_id 角色ID
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_permission_set_current_role(MenuRoleId role_id);
|
||||
|
||||
/**
|
||||
* @brief 获取当前角色
|
||||
* @return 当前角色ID
|
||||
*/
|
||||
MenuRoleId menu_permission_get_current_role(void);
|
||||
|
||||
/**
|
||||
* @brief 检查节点是否可访问
|
||||
* @param node_id 节点ID
|
||||
* @return true表示可访问,false表示不可访问
|
||||
*/
|
||||
bool menu_permission_check_node_access(MenuNodeId node_id);
|
||||
|
||||
/**
|
||||
* @brief 更新节点权限级别
|
||||
* @param node_id 节点ID
|
||||
* @param permission_level 权限级别
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_permission_update_node_level(MenuNodeId node_id, MenuPermissionLevel permission_level);
|
||||
|
||||
/**
|
||||
* @brief 获取节点权限级别
|
||||
* @param node_id 节点ID
|
||||
* @return 节点权限级别
|
||||
*/
|
||||
MenuPermissionLevel menu_permission_get_node_level(MenuNodeId node_id);
|
||||
|
||||
/**
|
||||
* @brief 更新所有节点的可见性和可用性
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_permission_update_all_nodes(void);
|
||||
|
||||
/**
|
||||
* @brief 获取角色信息
|
||||
* @param role_id 角色ID
|
||||
* @return 角色结构体指针,NULL表示角色不存在
|
||||
*/
|
||||
const MenuRole* menu_permission_get_role(MenuRoleId role_id);
|
||||
|
||||
#endif /* MENU_CONFIG_ENABLE_PERMISSION */
|
||||
|
||||
#endif /* MENU_PERMISSION_H */
|
||||
@ -1,441 +0,0 @@
|
||||
/**
|
||||
**********************************************************************************************************************
|
||||
* @file menu_persistence.c
|
||||
* @brief 菜单组件状态持久化功能实现
|
||||
* @author menu_component
|
||||
* @date 2025-12-19
|
||||
**********************************************************************************************************************
|
||||
*/
|
||||
|
||||
/* Includes ----------------------------------------------------------------------------------------------------------*/
|
||||
#include "menu_persistence.h"
|
||||
|
||||
/* 私有函数原型 -----------------------------------------------------------------------------------------------------*/
|
||||
|
||||
#if MENU_CONFIG_ENABLE_PERSISTENCE
|
||||
|
||||
/**
|
||||
* @brief 构建持久化数据
|
||||
* @param core_ctx 菜单核心上下文
|
||||
* @param data 持久化数据指针
|
||||
* @return 错误码
|
||||
*/
|
||||
static MenuErrCode menu_persistence_build_data(MenuCoreCtx* core_ctx, MenuPersistenceData* data);
|
||||
|
||||
/**
|
||||
* @brief 应用持久化数据
|
||||
* @param core_ctx 菜单核心上下文
|
||||
* @param data 持久化数据指针
|
||||
* @return 错误码
|
||||
*/
|
||||
static MenuErrCode menu_persistence_apply_data(MenuCoreCtx* core_ctx, const MenuPersistenceData* data);
|
||||
|
||||
/**
|
||||
* @brief 加密持久化数据
|
||||
* @param data 要加密的数据
|
||||
* @param size 数据大小
|
||||
* @return 错误码
|
||||
*/
|
||||
static MenuErrCode menu_persistence_encrypt(uint8_t* data, uint16_t size);
|
||||
|
||||
/**
|
||||
* @brief 解密持久化数据
|
||||
* @param data 要解密的数据
|
||||
* @param size 数据大小
|
||||
* @return 错误码
|
||||
*/
|
||||
static MenuErrCode menu_persistence_decrypt(uint8_t* data, uint16_t size);
|
||||
|
||||
/* 函数实现 ---------------------------------------------------------------------------------------------------------*/
|
||||
|
||||
/**
|
||||
* @brief 初始化状态持久化模块
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_persistence_init(void)
|
||||
{
|
||||
MenuCoreCtx* core_ctx = menu_get_core_ctx();
|
||||
|
||||
if (core_ctx == NULL) {
|
||||
return MENU_ERR_NOT_INITIALIZED;
|
||||
}
|
||||
|
||||
// 初始化持久化配置
|
||||
core_ctx->persistence_config.auto_save_enabled = true;
|
||||
core_ctx->persistence_config.auto_save_interval = MENU_CONFIG_PERSISTENCE_AUTO_SAVE_INTERVAL;
|
||||
core_ctx->persistence_config.last_save_tick = 0;
|
||||
core_ctx->persistence_config.encrypt_enabled = (MENU_CONFIG_PERSISTENCE_ENABLE_ENCRYPT == 1U);
|
||||
core_ctx->persistence_config.save_cb = NULL;
|
||||
core_ctx->persistence_config.restore_cb = NULL;
|
||||
|
||||
return MENU_ERR_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 反初始化状态持久化模块
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_persistence_deinit(void)
|
||||
{
|
||||
MenuCoreCtx* core_ctx = menu_get_core_ctx();
|
||||
|
||||
if (core_ctx == NULL) {
|
||||
return MENU_ERR_NOT_INITIALIZED;
|
||||
}
|
||||
|
||||
// 清空持久化配置
|
||||
core_ctx->persistence_config.save_cb = NULL;
|
||||
core_ctx->persistence_config.restore_cb = NULL;
|
||||
|
||||
return MENU_ERR_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 注册持久化回调函数
|
||||
* @param save_cb 保存回调函数
|
||||
* @param restore_cb 恢复回调函数
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_persistence_register_callback(MenuPersistenceCallback save_cb, MenuRestoreCallback restore_cb)
|
||||
{
|
||||
MenuCoreCtx* core_ctx = menu_get_core_ctx();
|
||||
|
||||
if (core_ctx == NULL) {
|
||||
return MENU_ERR_NOT_INITIALIZED;
|
||||
}
|
||||
|
||||
// 注册回调函数
|
||||
core_ctx->persistence_config.save_cb = save_cb;
|
||||
core_ctx->persistence_config.restore_cb = restore_cb;
|
||||
|
||||
return MENU_ERR_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 启用或禁用自动保存
|
||||
* @param enable true表示启用,false表示禁用
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_persistence_set_auto_save(bool enable)
|
||||
{
|
||||
MenuCoreCtx* core_ctx = menu_get_core_ctx();
|
||||
|
||||
if (core_ctx == NULL) {
|
||||
return MENU_ERR_NOT_INITIALIZED;
|
||||
}
|
||||
|
||||
core_ctx->persistence_config.auto_save_enabled = enable;
|
||||
|
||||
return MENU_ERR_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取自动保存状态
|
||||
* @return true表示启用,false表示禁用
|
||||
*/
|
||||
bool menu_persistence_get_auto_save(void)
|
||||
{
|
||||
MenuCoreCtx* core_ctx = menu_get_core_ctx();
|
||||
|
||||
if (core_ctx == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return core_ctx->persistence_config.auto_save_enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 设置自动保存间隔
|
||||
* @param interval 自动保存间隔(ms)
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_persistence_set_auto_save_interval(uint32_t interval)
|
||||
{
|
||||
MenuCoreCtx* core_ctx = menu_get_core_ctx();
|
||||
|
||||
if (core_ctx == NULL) {
|
||||
return MENU_ERR_NOT_INITIALIZED;
|
||||
}
|
||||
|
||||
core_ctx->persistence_config.auto_save_interval = interval;
|
||||
|
||||
return MENU_ERR_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取自动保存间隔
|
||||
* @return 自动保存间隔(ms)
|
||||
*/
|
||||
uint32_t menu_persistence_get_auto_save_interval(void)
|
||||
{
|
||||
MenuCoreCtx* core_ctx = menu_get_core_ctx();
|
||||
|
||||
if (core_ctx == NULL) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return core_ctx->persistence_config.auto_save_interval;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 手动保存菜单状态
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_persistence_save(void)
|
||||
{
|
||||
MenuCoreCtx* core_ctx = menu_get_core_ctx();
|
||||
MenuPersistenceData data;
|
||||
uint8_t* data_buf;
|
||||
uint16_t data_size = sizeof(MenuPersistenceData);
|
||||
MenuErrCode ret;
|
||||
|
||||
if (core_ctx == NULL) {
|
||||
return MENU_ERR_NOT_INITIALIZED;
|
||||
}
|
||||
|
||||
if (core_ctx->persistence_config.save_cb == NULL) {
|
||||
return MENU_ERR_INVALID_CALLBACK;
|
||||
}
|
||||
|
||||
// 构建持久化数据
|
||||
ret = menu_persistence_build_data(core_ctx, &data);
|
||||
if (ret != MENU_ERR_OK) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
// 转换为字节数组
|
||||
data_buf = (uint8_t*)&data;
|
||||
|
||||
// 如果启用加密,加密数据
|
||||
if (core_ctx->persistence_config.encrypt_enabled) {
|
||||
ret = menu_persistence_encrypt(data_buf, data_size);
|
||||
if (ret != MENU_ERR_OK) {
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
// 调用保存回调函数
|
||||
ret = core_ctx->persistence_config.save_cb(data_buf, data_size);
|
||||
if (ret == MENU_ERR_OK) {
|
||||
// 更新上次保存时间
|
||||
core_ctx->persistence_config.last_save_tick = core_ctx->last_refresh_tick;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 恢复菜单状态
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_persistence_restore(void)
|
||||
{
|
||||
MenuCoreCtx* core_ctx = menu_get_core_ctx();
|
||||
uint8_t data_buf[MENU_CONFIG_PERSISTENCE_MAX_SIZE];
|
||||
uint16_t data_size = sizeof(MenuPersistenceData);
|
||||
MenuPersistenceData* data = (MenuPersistenceData*)data_buf;
|
||||
MenuErrCode ret;
|
||||
|
||||
if (core_ctx == NULL) {
|
||||
return MENU_ERR_NOT_INITIALIZED;
|
||||
}
|
||||
|
||||
if (core_ctx->persistence_config.restore_cb == NULL) {
|
||||
return MENU_ERR_INVALID_CALLBACK;
|
||||
}
|
||||
|
||||
// 调用恢复回调函数
|
||||
ret = core_ctx->persistence_config.restore_cb(data_buf, &data_size);
|
||||
if (ret != MENU_ERR_OK) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
// 检查数据大小是否正确
|
||||
if (data_size != sizeof(MenuPersistenceData)) {
|
||||
return MENU_ERR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
// 如果启用加密,解密数据
|
||||
if (core_ctx->persistence_config.encrypt_enabled) {
|
||||
ret = menu_persistence_decrypt(data_buf, data_size);
|
||||
if (ret != MENU_ERR_OK) {
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
// 检查数据是否有效
|
||||
if (!menu_persistence_check_data_valid(data_buf, data_size)) {
|
||||
return MENU_ERR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
// 应用持久化数据
|
||||
return menu_persistence_apply_data(core_ctx, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 检查持久化数据是否有效
|
||||
* @param data 持久化数据
|
||||
* @param size 数据大小
|
||||
* @return true表示有效,false表示无效
|
||||
*/
|
||||
bool menu_persistence_check_data_valid(const uint8_t* data, uint16_t size)
|
||||
{
|
||||
const MenuPersistenceData* persistence_data;
|
||||
uint8_t calculated_checksum;
|
||||
|
||||
if (data == NULL || size != sizeof(MenuPersistenceData)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
persistence_data = (const MenuPersistenceData*)data;
|
||||
|
||||
// 计算校验和
|
||||
calculated_checksum = menu_persistence_calculate_checksum(data, size - sizeof(uint8_t));
|
||||
|
||||
// 检查校验和
|
||||
return (calculated_checksum == persistence_data->checksum);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 处理自动保存(由菜单主循环调用)
|
||||
* @param tick 当前系统时间(ms)
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_persistence_process_auto_save(uint32_t tick)
|
||||
{
|
||||
MenuCoreCtx* core_ctx = menu_get_core_ctx();
|
||||
|
||||
if (core_ctx == NULL) {
|
||||
return MENU_ERR_NOT_INITIALIZED;
|
||||
}
|
||||
|
||||
// 如果启用自动保存,检查是否需要保存
|
||||
if (core_ctx->persistence_config.auto_save_enabled) {
|
||||
if ((tick - core_ctx->persistence_config.last_save_tick) >= core_ctx->persistence_config.auto_save_interval) {
|
||||
return menu_persistence_save();
|
||||
}
|
||||
}
|
||||
|
||||
return MENU_ERR_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 计算数据校验和
|
||||
* @param data 数据指针
|
||||
* @param size 数据大小
|
||||
* @return 校验和
|
||||
*/
|
||||
uint8_t menu_persistence_calculate_checksum(const uint8_t* data, uint16_t size)
|
||||
{
|
||||
uint8_t checksum = 0;
|
||||
|
||||
if (data == NULL || size == 0) {
|
||||
return checksum;
|
||||
}
|
||||
|
||||
// 计算简单的异或校验和
|
||||
for (uint16_t i = 0; i < size; i++) {
|
||||
checksum ^= data[i];
|
||||
}
|
||||
|
||||
return checksum;
|
||||
}
|
||||
|
||||
/* 私有函数实现 -----------------------------------------------------------------------------------------------------*/
|
||||
|
||||
/**
|
||||
* @brief 构建持久化数据
|
||||
* @param core_ctx 菜单核心上下文
|
||||
* @param data 持久化数据指针
|
||||
* @return 错误码
|
||||
*/
|
||||
static MenuErrCode menu_persistence_build_data(MenuCoreCtx* core_ctx, MenuPersistenceData* data)
|
||||
{
|
||||
if (core_ctx == NULL || data == NULL) {
|
||||
return MENU_ERR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
// 填充持久化数据
|
||||
data->current_node_id = core_ctx->current_node_id;
|
||||
data->current_state = core_ctx->current_state;
|
||||
data->nav_path = core_ctx->nav_path;
|
||||
data->stack = core_ctx->stack;
|
||||
data->timestamp = core_ctx->last_refresh_tick;
|
||||
|
||||
// 计算校验和(不包括校验和字段本身)
|
||||
data->checksum = menu_persistence_calculate_checksum((uint8_t*)data, sizeof(MenuPersistenceData) - sizeof(uint8_t));
|
||||
|
||||
return MENU_ERR_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 应用持久化数据
|
||||
* @param core_ctx 菜单核心上下文
|
||||
* @param data 持久化数据指针
|
||||
* @return 错误码
|
||||
*/
|
||||
static MenuErrCode menu_persistence_apply_data(MenuCoreCtx* core_ctx, const MenuPersistenceData* data)
|
||||
{
|
||||
if (core_ctx == NULL || data == NULL) {
|
||||
return MENU_ERR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
// 恢复菜单状态
|
||||
core_ctx->current_node_id = data->current_node_id;
|
||||
core_ctx->current_state = data->current_state;
|
||||
core_ctx->nav_path = data->nav_path;
|
||||
core_ctx->stack = data->stack;
|
||||
|
||||
return MENU_ERR_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 加密持久化数据
|
||||
* @param data 要加密的数据
|
||||
* @param size 数据大小
|
||||
* @return 错误码
|
||||
*/
|
||||
static MenuErrCode menu_persistence_encrypt(uint8_t* data, uint16_t size)
|
||||
{
|
||||
// 简单的XOR加密实现
|
||||
// 实际应用中应使用更安全的加密算法
|
||||
uint8_t key = 0xA5; // 加密密钥
|
||||
|
||||
if (data == NULL || size == 0) {
|
||||
return MENU_ERR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
for (uint16_t i = 0; i < size; i++) {
|
||||
data[i] ^= key;
|
||||
key = (key + data[i]) % 256; // 动态更新密钥
|
||||
}
|
||||
|
||||
return MENU_ERR_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 解密持久化数据
|
||||
* @param data 要解密的数据
|
||||
* @param size 数据大小
|
||||
* @return 错误码
|
||||
*/
|
||||
static MenuErrCode menu_persistence_decrypt(uint8_t* data, uint16_t size)
|
||||
{
|
||||
// 简单的XOR解密实现(与加密算法对称)
|
||||
uint8_t key = 0xA5; // 解密密钥
|
||||
uint8_t temp;
|
||||
|
||||
if (data == NULL || size == 0) {
|
||||
return MENU_ERR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
for (uint16_t i = 0; i < size; i++) {
|
||||
temp = data[i];
|
||||
data[i] ^= key;
|
||||
key = (key + temp) % 256; // 动态更新密钥
|
||||
}
|
||||
|
||||
return MENU_ERR_OK;
|
||||
}
|
||||
|
||||
#endif /* MENU_CONFIG_ENABLE_PERSISTENCE */
|
||||
@ -1,103 +0,0 @@
|
||||
/**
|
||||
**********************************************************************************************************************
|
||||
* @file menu_persistence.h
|
||||
* @brief 菜单组件状态持久化功能声明
|
||||
* @author menu_component
|
||||
* @date 2025-12-19
|
||||
**********************************************************************************************************************
|
||||
*/
|
||||
|
||||
#ifndef MENU_PERSISTENCE_H
|
||||
#define MENU_PERSISTENCE_H
|
||||
|
||||
/* Includes ----------------------------------------------------------------------------------------------------------*/
|
||||
#include "menu_types.h"
|
||||
|
||||
/* 函数声明 ---------------------------------------------------------------------------------------------------------*/
|
||||
|
||||
#if MENU_CONFIG_ENABLE_PERSISTENCE
|
||||
|
||||
/**
|
||||
* @brief 初始化状态持久化模块
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_persistence_init(void);
|
||||
|
||||
/**
|
||||
* @brief 反初始化状态持久化模块
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_persistence_deinit(void);
|
||||
|
||||
/**
|
||||
* @brief 注册持久化回调函数
|
||||
* @param save_cb 保存回调函数
|
||||
* @param restore_cb 恢复回调函数
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_persistence_register_callback(MenuPersistenceCallback save_cb, MenuRestoreCallback restore_cb);
|
||||
|
||||
/**
|
||||
* @brief 启用或禁用自动保存
|
||||
* @param enable true表示启用,false表示禁用
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_persistence_set_auto_save(bool enable);
|
||||
|
||||
/**
|
||||
* @brief 获取自动保存状态
|
||||
* @return true表示启用,false表示禁用
|
||||
*/
|
||||
bool menu_persistence_get_auto_save(void);
|
||||
|
||||
/**
|
||||
* @brief 设置自动保存间隔
|
||||
* @param interval 自动保存间隔(ms)
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_persistence_set_auto_save_interval(uint32_t interval);
|
||||
|
||||
/**
|
||||
* @brief 获取自动保存间隔
|
||||
* @return 自动保存间隔(ms)
|
||||
*/
|
||||
uint32_t menu_persistence_get_auto_save_interval(void);
|
||||
|
||||
/**
|
||||
* @brief 手动保存菜单状态
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_persistence_save(void);
|
||||
|
||||
/**
|
||||
* @brief 恢复菜单状态
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_persistence_restore(void);
|
||||
|
||||
/**
|
||||
* @brief 检查持久化数据是否有效
|
||||
* @param data 持久化数据
|
||||
* @param size 数据大小
|
||||
* @return true表示有效,false表示无效
|
||||
*/
|
||||
bool menu_persistence_check_data_valid(const uint8_t* data, uint16_t size);
|
||||
|
||||
/**
|
||||
* @brief 处理自动保存(由菜单主循环调用)
|
||||
* @param tick 当前系统时间(ms)
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_persistence_process_auto_save(uint32_t tick);
|
||||
|
||||
/**
|
||||
* @brief 计算数据校验和
|
||||
* @param data 数据指针
|
||||
* @param size 数据大小
|
||||
* @return 校验和
|
||||
*/
|
||||
uint8_t menu_persistence_calculate_checksum(const uint8_t* data, uint16_t size);
|
||||
|
||||
#endif /* MENU_CONFIG_ENABLE_PERSISTENCE */
|
||||
|
||||
#endif /* MENU_PERSISTENCE_H */
|
||||
@ -1,215 +0,0 @@
|
||||
/**
|
||||
**********************************************************************************************************************
|
||||
* @file menu_stack.c
|
||||
* @brief 菜单组件栈管理实现
|
||||
* @author menu_component
|
||||
* @date 2025-12-19
|
||||
**********************************************************************************************************************
|
||||
*/
|
||||
|
||||
/* Includes ----------------------------------------------------------------------------------------------------------*/
|
||||
#include "menu_types.h"
|
||||
|
||||
/* 函数实现 ---------------------------------------------------------------------------------------------------------*/
|
||||
|
||||
/**
|
||||
* @brief 初始化菜单栈
|
||||
* @param stack 栈指针
|
||||
* @param max_depth 最大深度
|
||||
*/
|
||||
void menu_stack_init(MenuStack* stack, uint8_t max_depth)
|
||||
{
|
||||
MENU_ASSERT(stack != NULL);
|
||||
|
||||
stack->top = 0;
|
||||
stack->max_depth = max_depth;
|
||||
|
||||
// 初始化栈元素为0
|
||||
for (uint8_t i = 0; i < max_depth; i++) {
|
||||
stack->items[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 检查栈是否为空
|
||||
* @param stack 栈指针
|
||||
* @return true为空,false不为空
|
||||
*/
|
||||
bool menu_stack_is_empty(const MenuStack* stack)
|
||||
{
|
||||
MENU_ASSERT(stack != NULL);
|
||||
return (stack->top == 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 检查栈是否已满
|
||||
* @param stack 栈指针
|
||||
* @return true已满,false未满
|
||||
*/
|
||||
bool menu_stack_is_full(const MenuStack* stack)
|
||||
{
|
||||
MENU_ASSERT(stack != NULL);
|
||||
return (stack->top >= stack->max_depth);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取栈的当前深度
|
||||
* @param stack 栈指针
|
||||
* @return 当前深度
|
||||
*/
|
||||
uint8_t menu_stack_get_depth(const MenuStack* stack)
|
||||
{
|
||||
MENU_ASSERT(stack != NULL);
|
||||
return stack->top;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 向栈中压入元素
|
||||
* @param stack 栈指针
|
||||
* @param node_id 要压入的节点ID
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_stack_push(MenuStack* stack, MenuNodeId node_id)
|
||||
{
|
||||
MENU_ASSERT(stack != NULL);
|
||||
|
||||
if (menu_stack_is_full(stack)) {
|
||||
return MENU_ERR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
stack->items[stack->top] = node_id;
|
||||
stack->top++;
|
||||
|
||||
return MENU_ERR_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 从栈中弹出元素
|
||||
* @param stack 栈指针
|
||||
* @param node_id 用于存储弹出的节点ID
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_stack_pop(MenuStack* stack, MenuNodeId* node_id)
|
||||
{
|
||||
MENU_ASSERT(stack != NULL);
|
||||
MENU_ASSERT(node_id != NULL);
|
||||
|
||||
if (menu_stack_is_empty(stack)) {
|
||||
return MENU_ERR_QUEUE_EMPTY;
|
||||
}
|
||||
|
||||
stack->top--;
|
||||
*node_id = stack->items[stack->top];
|
||||
stack->items[stack->top] = 0;
|
||||
|
||||
return MENU_ERR_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取栈顶元素
|
||||
* @param stack 栈指针
|
||||
* @param node_id 用于存储栈顶节点ID
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_stack_peek(const MenuStack* stack, MenuNodeId* node_id)
|
||||
{
|
||||
MENU_ASSERT(stack != NULL);
|
||||
MENU_ASSERT(node_id != NULL);
|
||||
|
||||
if (menu_stack_is_empty(stack)) {
|
||||
return MENU_ERR_QUEUE_EMPTY;
|
||||
}
|
||||
|
||||
*node_id = stack->items[stack->top - 1];
|
||||
|
||||
return MENU_ERR_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 清空栈
|
||||
* @param stack 栈指针
|
||||
*/
|
||||
void menu_stack_clear(MenuStack* stack)
|
||||
{
|
||||
MENU_ASSERT(stack != NULL);
|
||||
|
||||
stack->top = 0;
|
||||
|
||||
// 清空栈元素
|
||||
for (uint8_t i = 0; i < stack->max_depth; i++) {
|
||||
stack->items[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 初始化菜单导航路径
|
||||
* @param nav_path 导航路径指针
|
||||
* @param max_depth 最大深度
|
||||
*/
|
||||
void menu_nav_path_init(MenuNavPath* nav_path, uint8_t max_depth)
|
||||
{
|
||||
MENU_ASSERT(nav_path != NULL);
|
||||
|
||||
nav_path->depth = 0;
|
||||
nav_path->max_depth = max_depth;
|
||||
|
||||
// 初始化路径为0
|
||||
for (uint8_t i = 0; i < max_depth; i++) {
|
||||
nav_path->path[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 添加导航路径节点
|
||||
* @param nav_path 导航路径指针
|
||||
* @param node_id 要添加的节点ID
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_nav_path_add(MenuNavPath* nav_path, MenuNodeId node_id)
|
||||
{
|
||||
MENU_ASSERT(nav_path != NULL);
|
||||
|
||||
if (nav_path->depth >= nav_path->max_depth) {
|
||||
return MENU_ERR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
nav_path->path[nav_path->depth] = node_id;
|
||||
nav_path->depth++;
|
||||
|
||||
return MENU_ERR_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 移除导航路径最后一个节点
|
||||
* @param nav_path 导航路径指针
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_nav_path_remove_last(MenuNavPath* nav_path)
|
||||
{
|
||||
MENU_ASSERT(nav_path != NULL);
|
||||
|
||||
if (nav_path->depth == 0) {
|
||||
return MENU_ERR_QUEUE_EMPTY;
|
||||
}
|
||||
|
||||
nav_path->depth--;
|
||||
nav_path->path[nav_path->depth] = 0;
|
||||
|
||||
return MENU_ERR_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 清空导航路径
|
||||
* @param nav_path 导航路径指针
|
||||
*/
|
||||
void menu_nav_path_clear(MenuNavPath* nav_path)
|
||||
{
|
||||
MENU_ASSERT(nav_path != NULL);
|
||||
|
||||
nav_path->depth = 0;
|
||||
|
||||
// 清空路径
|
||||
for (uint8_t i = 0; i < nav_path->max_depth; i++) {
|
||||
nav_path->path[i] = 0;
|
||||
}
|
||||
}
|
||||
@ -1,104 +0,0 @@
|
||||
/**
|
||||
**********************************************************************************************************************
|
||||
* @file menu_stack.h
|
||||
* @brief 菜单组件栈管理功能声明
|
||||
* @author menu_component
|
||||
* @date 2025-12-19
|
||||
**********************************************************************************************************************
|
||||
*/
|
||||
|
||||
#ifndef MENU_STACK_H
|
||||
#define MENU_STACK_H
|
||||
|
||||
/* Includes ----------------------------------------------------------------------------------------------------------*/
|
||||
#include "menu_types.h"
|
||||
|
||||
/* 函数声明 ---------------------------------------------------------------------------------------------------------*/
|
||||
|
||||
/**
|
||||
* @brief 初始化菜单栈
|
||||
* @param stack 栈指针
|
||||
* @param max_depth 最大深度
|
||||
*/
|
||||
void menu_stack_init(MenuStack* stack, uint8_t max_depth);
|
||||
|
||||
/**
|
||||
* @brief 检查栈是否为空
|
||||
* @param stack 栈指针
|
||||
* @return true为空,false不为空
|
||||
*/
|
||||
bool menu_stack_is_empty(const MenuStack* stack);
|
||||
|
||||
/**
|
||||
* @brief 检查栈是否已满
|
||||
* @param stack 栈指针
|
||||
* @return true已满,false未满
|
||||
*/
|
||||
bool menu_stack_is_full(const MenuStack* stack);
|
||||
|
||||
/**
|
||||
* @brief 获取栈的当前深度
|
||||
* @param stack 栈指针
|
||||
* @return 当前深度
|
||||
*/
|
||||
uint8_t menu_stack_get_depth(const MenuStack* stack);
|
||||
|
||||
/**
|
||||
* @brief 向栈中压入元素
|
||||
* @param stack 栈指针
|
||||
* @param node_id 要压入的节点ID
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_stack_push(MenuStack* stack, MenuNodeId node_id);
|
||||
|
||||
/**
|
||||
* @brief 从栈中弹出元素
|
||||
* @param stack 栈指针
|
||||
* @param node_id 用于存储弹出的节点ID
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_stack_pop(MenuStack* stack, MenuNodeId* node_id);
|
||||
|
||||
/**
|
||||
* @brief 获取栈顶元素
|
||||
* @param stack 栈指针
|
||||
* @param node_id 用于存储栈顶节点ID
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_stack_peek(const MenuStack* stack, MenuNodeId* node_id);
|
||||
|
||||
/**
|
||||
* @brief 清空栈
|
||||
* @param stack 栈指针
|
||||
*/
|
||||
void menu_stack_clear(MenuStack* stack);
|
||||
|
||||
/**
|
||||
* @brief 初始化菜单导航路径
|
||||
* @param nav_path 导航路径指针
|
||||
* @param max_depth 最大深度
|
||||
*/
|
||||
void menu_nav_path_init(MenuNavPath* nav_path, uint8_t max_depth);
|
||||
|
||||
/**
|
||||
* @brief 添加导航路径节点
|
||||
* @param nav_path 导航路径指针
|
||||
* @param node_id 要添加的节点ID
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_nav_path_add(MenuNavPath* nav_path, MenuNodeId node_id);
|
||||
|
||||
/**
|
||||
* @brief 移除导航路径最后一个节点
|
||||
* @param nav_path 导航路径指针
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_nav_path_remove_last(MenuNavPath* nav_path);
|
||||
|
||||
/**
|
||||
* @brief 清空导航路径
|
||||
* @param nav_path 导航路径指针
|
||||
*/
|
||||
void menu_nav_path_clear(MenuNavPath* nav_path);
|
||||
|
||||
#endif /* MENU_STACK_H */
|
||||
@ -1,233 +0,0 @@
|
||||
/**
|
||||
**********************************************************************************************************************
|
||||
* @file menu_types.h
|
||||
* @brief 菜单组件核心数据结构定义
|
||||
* @author menu_component
|
||||
* @date 2025-12-19
|
||||
**********************************************************************************************************************
|
||||
*/
|
||||
|
||||
#ifndef MENU_TYPES_H
|
||||
#define MENU_TYPES_H
|
||||
|
||||
/* Includes ----------------------------------------------------------------------------------------------------------*/
|
||||
#include "menu_config.h"
|
||||
|
||||
/* 前置声明 ---------------------------------------------------------------------------------------------------------*/
|
||||
struct MenuNode;
|
||||
struct MenuCoreCtx;
|
||||
struct MenuEvent;
|
||||
struct MenuStateTransition;
|
||||
|
||||
/* 函数前向声明 -----------------------------------------------------------------------------------------------------*/
|
||||
|
||||
/**
|
||||
* @brief 获取菜单核心上下文
|
||||
* @return 菜单核心上下文指针
|
||||
*/
|
||||
struct MenuCoreCtx* menu_get_core_ctx(void);
|
||||
|
||||
/* 回调函数类型定义 -------------------------------------------------------------------------------------------------*/
|
||||
|
||||
/**
|
||||
* @brief 菜单进入回调函数类型
|
||||
* @param node_id 当前节点ID
|
||||
* @param core_ctx 菜单核心上下文
|
||||
* @return 错误码
|
||||
*/
|
||||
typedef MenuErrCode (*MenuCallback)(MenuNodeId node_id, struct MenuCoreCtx* core_ctx);
|
||||
|
||||
/**
|
||||
* @brief 状态转换动作函数类型
|
||||
* @param core_ctx 菜单核心上下文
|
||||
* @return 错误码
|
||||
*/
|
||||
typedef MenuErrCode (*MenuStateAction)(struct MenuCoreCtx* core_ctx);
|
||||
|
||||
/**
|
||||
* @brief 持久化回调函数类型
|
||||
* @param data 要持久化的数据
|
||||
* @param size 数据大小
|
||||
* @return 错误码
|
||||
*/
|
||||
typedef MenuErrCode (*MenuPersistenceCallback)(const uint8_t* data, uint16_t size);
|
||||
|
||||
/**
|
||||
* @brief 恢复回调函数类型
|
||||
* @param data 要恢复的数据
|
||||
* @param size 数据大小
|
||||
* @return 错误码
|
||||
*/
|
||||
typedef MenuErrCode (*MenuRestoreCallback)(uint8_t* data, uint16_t* size);
|
||||
|
||||
/* 权限管理相关定义 -------------------------------------------------------------------------------------------------*/
|
||||
|
||||
/**
|
||||
* @brief 权限级别类型
|
||||
*/
|
||||
typedef uint8_t MenuPermissionLevel;
|
||||
|
||||
/**
|
||||
* @brief 角色ID类型
|
||||
*/
|
||||
typedef uint8_t MenuRoleId;
|
||||
|
||||
/**
|
||||
* @brief 角色结构体
|
||||
*/
|
||||
typedef struct {
|
||||
MenuRoleId id; ///< 角色ID
|
||||
const char* name; ///< 角色名称
|
||||
MenuPermissionLevel max_level; ///< 该角色的最大权限级别
|
||||
} MenuRole;
|
||||
|
||||
/**
|
||||
* @brief 权限配置结构体
|
||||
*/
|
||||
typedef struct {
|
||||
MenuRoleId current_role; ///< 当前角色ID
|
||||
MenuRole roles[MENU_CONFIG_PERMISSION_MAX_ROLES]; ///< 角色列表
|
||||
uint8_t role_count; ///< 角色数量
|
||||
} MenuPermissionConfig;
|
||||
|
||||
/* 核心数据结构定义 -------------------------------------------------------------------------------------------------*/
|
||||
|
||||
/**
|
||||
* @brief 菜单节点结构体
|
||||
*/
|
||||
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;
|
||||
#if MENU_CONFIG_ENABLE_PARAM
|
||||
uint16_t param_id; ///< 绑定的参数ID(启用参数时有效)
|
||||
#endif
|
||||
#if MENU_CONFIG_ENABLE_PERMISSION
|
||||
MenuPermissionLevel permission_level; ///< 访问该菜单所需的权限级别
|
||||
#endif
|
||||
} MenuNode;
|
||||
|
||||
/**
|
||||
* @brief 菜单事件结构体
|
||||
*/
|
||||
typedef struct MenuEvent {
|
||||
MenuEventType type; ///< 事件类型
|
||||
uint8_t priority; ///< 事件优先级(0-3,0最高)
|
||||
MenuNodeId target_node_id; ///< 目标节点ID
|
||||
void* data; ///< 事件数据
|
||||
uint32_t timestamp; ///< 事件时间戳
|
||||
} MenuEvent;
|
||||
|
||||
/**
|
||||
* @brief 菜单事件队列结构体
|
||||
*/
|
||||
typedef struct MenuEventQueue {
|
||||
MenuEvent buffer[MENU_CONFIG_EVENT_QUEUE_LEN]; ///< 队列缓冲区
|
||||
uint8_t head; ///< 入队指针
|
||||
uint8_t tail; ///< 出队指针
|
||||
uint8_t count; ///< 队列元素数量
|
||||
uint8_t reserved; ///< 保留字节,用于对齐
|
||||
} MenuEventQueue;
|
||||
|
||||
/**
|
||||
* @brief 菜单导航路径结构体
|
||||
*/
|
||||
typedef struct MenuNavPath {
|
||||
MenuNodeId path[MENU_CONFIG_STACK_DEPTH]; ///< 导航路径
|
||||
uint8_t depth; ///< 当前深度
|
||||
uint8_t max_depth; ///< 最大深度
|
||||
} MenuNavPath;
|
||||
|
||||
/**
|
||||
* @brief 菜单栈结构体
|
||||
*/
|
||||
typedef struct MenuStack {
|
||||
MenuNodeId items[MENU_CONFIG_STACK_DEPTH]; ///< 栈元素
|
||||
uint8_t top; ///< 栈顶指针
|
||||
uint8_t max_depth; ///< 最大深度
|
||||
} MenuStack;
|
||||
|
||||
/* 状态持久化相关定义 -------------------------------------------------------------------------------------------------*/
|
||||
|
||||
/**
|
||||
* @brief 持久化数据结构体
|
||||
*/
|
||||
typedef struct {
|
||||
MenuNodeId current_node_id; ///< 当前节点ID
|
||||
MenuState current_state; ///< 当前菜单状态
|
||||
MenuNavPath nav_path; ///< 当前导航路径
|
||||
MenuStack stack; ///< 当前菜单栈
|
||||
uint32_t timestamp; ///< 持久化时间戳
|
||||
uint8_t checksum; ///< 数据校验和
|
||||
} MenuPersistenceData;
|
||||
|
||||
/**
|
||||
* @brief 持久化配置结构体
|
||||
*/
|
||||
typedef struct {
|
||||
bool auto_save_enabled; ///< 是否启用自动保存
|
||||
uint32_t auto_save_interval; ///< 自动保存间隔(ms)
|
||||
uint32_t last_save_tick; ///< 上次保存时间
|
||||
bool encrypt_enabled; ///< 是否启用加密
|
||||
MenuPersistenceCallback save_cb; ///< 保存回调函数
|
||||
MenuRestoreCallback restore_cb; ///< 恢复回调函数
|
||||
} MenuPersistenceConfig;
|
||||
|
||||
/**
|
||||
* @brief 状态转换规则结构体
|
||||
*/
|
||||
typedef struct MenuStateTransition {
|
||||
MenuState current_state; ///< 当前状态
|
||||
MenuEventType event; ///< 触发事件
|
||||
MenuState next_state; ///< 下一个状态
|
||||
MenuStateAction action; ///< 状态转换动作
|
||||
const char* desc; ///< 状态转换描述(用于调试)
|
||||
} MenuStateTransition;
|
||||
|
||||
/**
|
||||
* @brief 菜单核心上下文结构体
|
||||
*/
|
||||
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; ///< 上次刷新时间(ms)
|
||||
bool is_initialized; ///< 是否已初始化
|
||||
MenuErrCode error_code; ///< 当前错误码
|
||||
const char* error_msg; ///< 当前错误信息
|
||||
uint16_t node_count; ///< 已注册节点数量
|
||||
uint16_t free_node_count; ///< 空闲节点数量
|
||||
// 状态机扩展
|
||||
#if MENU_CONFIG_ENABLE_STATE_MACHINE_EXT
|
||||
MenuStateTransition custom_transitions[MENU_CONFIG_MAX_STATE_TRANSITIONS]; ///< 自定义状态转换规则
|
||||
uint16_t custom_transition_count; ///< 自定义状态转换规则数量
|
||||
#endif
|
||||
// 权限管理
|
||||
#if MENU_CONFIG_ENABLE_PERMISSION
|
||||
MenuPermissionConfig permission_config; ///< 权限配置
|
||||
#endif
|
||||
// 状态持久化
|
||||
#if MENU_CONFIG_ENABLE_PERSISTENCE
|
||||
MenuPersistenceConfig persistence_config; ///< 持久化配置
|
||||
#endif
|
||||
} MenuCoreCtx;
|
||||
|
||||
#endif /* MENU_TYPES_H */
|
||||
@ -1,228 +0,0 @@
|
||||
/**
|
||||
**********************************************************************************************************************
|
||||
* @file menu_lang.c
|
||||
* @brief 菜单组件多语言支持模块实现
|
||||
* @author menu_component
|
||||
* @date 2025-12-19
|
||||
**********************************************************************************************************************
|
||||
*/
|
||||
|
||||
/* Includes ----------------------------------------------------------------------------------------------------------*/
|
||||
#include "menu_lang.h"
|
||||
|
||||
/* 配置参数 ---------------------------------------------------------------------------------------------------------*/
|
||||
#define MENU_LANG_MAX_PACKS 8U /* 最大支持的语言包数量 */
|
||||
|
||||
/* 全局变量 ---------------------------------------------------------------------------------------------------------*/
|
||||
static const LangPack* sg_lang_packs[MENU_LANG_MAX_PACKS];
|
||||
static uint8_t sg_lang_pack_count = 0;
|
||||
static LangId sg_current_lang_id = 0;
|
||||
|
||||
/* 函数实现 ---------------------------------------------------------------------------------------------------------*/
|
||||
|
||||
#if MENU_CONFIG_ENABLE_LANG
|
||||
|
||||
/**
|
||||
* @brief 初始化多语言支持模块
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_lang_init(void)
|
||||
{
|
||||
// 初始化语言包数组
|
||||
for (uint8_t i = 0; i < MENU_LANG_MAX_PACKS; i++) {
|
||||
sg_lang_packs[i] = NULL;
|
||||
}
|
||||
|
||||
sg_lang_pack_count = 0;
|
||||
sg_current_lang_id = 0;
|
||||
return MENU_ERR_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 反初始化多语言支持模块
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_lang_deinit(void)
|
||||
{
|
||||
sg_lang_pack_count = 0;
|
||||
return MENU_ERR_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 设置当前语言
|
||||
* @param lang_id 语言ID
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_lang_set_current(LangId lang_id)
|
||||
{
|
||||
// 检查语言包是否已注册
|
||||
bool found = false;
|
||||
for (uint8_t i = 0; i < sg_lang_pack_count; i++) {
|
||||
if (sg_lang_packs[i]->id == lang_id) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
return MENU_ERR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
sg_current_lang_id = lang_id;
|
||||
return MENU_ERR_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取当前语言ID
|
||||
* @return 当前语言ID
|
||||
*/
|
||||
LangId menu_lang_get_current(void)
|
||||
{
|
||||
return sg_current_lang_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 查找语言包
|
||||
* @param lang_id 语言ID
|
||||
* @return 语言包指针,未找到返回NULL
|
||||
*/
|
||||
static const LangPack* menu_lang_find_pack_internal(LangId lang_id)
|
||||
{
|
||||
for (uint8_t i = 0; i < sg_lang_pack_count; i++) {
|
||||
if (sg_lang_packs[i]->id == lang_id) {
|
||||
return sg_lang_packs[i];
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 注册语言包
|
||||
* @param pack 语言包指针
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_lang_register_pack(const LangPack* pack)
|
||||
{
|
||||
if (pack == NULL) {
|
||||
return MENU_ERR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
// 检查语言包是否已存在
|
||||
if (menu_lang_find_pack_internal(pack->id) != NULL) {
|
||||
return MENU_ERR_OPERATION_FAILED;
|
||||
}
|
||||
|
||||
// 检查语言包数量是否超过最大值
|
||||
if (sg_lang_pack_count >= MENU_LANG_MAX_PACKS) {
|
||||
return MENU_ERR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
// 注册语言包
|
||||
sg_lang_packs[sg_lang_pack_count] = pack;
|
||||
sg_lang_pack_count++;
|
||||
|
||||
return MENU_ERR_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 注销语言包
|
||||
* @param lang_id 语言ID
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_lang_unregister_pack(LangId lang_id)
|
||||
{
|
||||
// 查找语言包在数组中的位置
|
||||
uint8_t index = 0;
|
||||
bool found = false;
|
||||
|
||||
for (index = 0; index < sg_lang_pack_count; index++) {
|
||||
if (sg_lang_packs[index]->id == lang_id) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
return MENU_ERR_NODE_NOT_FOUND;
|
||||
}
|
||||
|
||||
// 将后续语言包前移
|
||||
for (uint8_t i = index; i < sg_lang_pack_count - 1; i++) {
|
||||
sg_lang_packs[i] = sg_lang_packs[i + 1];
|
||||
}
|
||||
|
||||
sg_lang_pack_count--;
|
||||
|
||||
// 如果当前语言被注销,切换到第一个语言包
|
||||
if (sg_current_lang_id == lang_id && sg_lang_pack_count > 0) {
|
||||
sg_current_lang_id = sg_lang_packs[0]->id;
|
||||
}
|
||||
|
||||
return MENU_ERR_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 根据字符串ID获取当前语言的字符串
|
||||
* @param str_id 字符串ID
|
||||
* @return 字符串指针,未找到返回NULL
|
||||
*/
|
||||
const char* menu_lang_get_str(StrId str_id)
|
||||
{
|
||||
// 获取当前语言包
|
||||
const LangPack* pack = menu_lang_find_pack_internal(sg_current_lang_id);
|
||||
if (pack == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// 在当前语言包中查找字符串
|
||||
for (uint16_t i = 0; i < pack->string_count; i++) {
|
||||
if (pack->strings[i].id == str_id) {
|
||||
return pack->strings[i].str;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果在当前语言包中未找到,尝试在第一个语言包中查找
|
||||
if (sg_lang_pack_count > 0 && pack != sg_lang_packs[0]) {
|
||||
pack = sg_lang_packs[0];
|
||||
for (uint16_t i = 0; i < pack->string_count; i++) {
|
||||
if (pack->strings[i].id == str_id) {
|
||||
return pack->strings[i].str;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 加载语言包
|
||||
* @param pack 语言包指针
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_lang_load_pack(const LangPack* pack)
|
||||
{
|
||||
if (pack == NULL) {
|
||||
return MENU_ERR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
// 注册语言包
|
||||
MenuErrCode err = menu_lang_register_pack(pack);
|
||||
if (err != MENU_ERR_OK) {
|
||||
return err;
|
||||
}
|
||||
|
||||
// 设置为当前语言
|
||||
return menu_lang_set_current(pack->id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 查找语言包
|
||||
* @param lang_id 语言ID
|
||||
* @return 语言包指针,未找到返回NULL
|
||||
*/
|
||||
const LangPack* menu_lang_find_pack(LangId lang_id)
|
||||
{
|
||||
return menu_lang_find_pack_internal(lang_id);
|
||||
}
|
||||
|
||||
#endif /* MENU_CONFIG_ENABLE_LANG */
|
||||
@ -1,125 +0,0 @@
|
||||
/**
|
||||
**********************************************************************************************************************
|
||||
* @file menu_lang.h
|
||||
* @brief 菜单组件多语言支持模块
|
||||
* @author menu_component
|
||||
* @date 2025-12-19
|
||||
**********************************************************************************************************************
|
||||
*/
|
||||
|
||||
#ifndef MENU_LANG_H
|
||||
#define MENU_LANG_H
|
||||
|
||||
/* Includes ----------------------------------------------------------------------------------------------------------*/
|
||||
#include "../core/menu_config.h"
|
||||
|
||||
/* 类型定义 ---------------------------------------------------------------------------------------------------------*/
|
||||
|
||||
/**
|
||||
* @brief 语言ID类型
|
||||
*/
|
||||
typedef uint8_t LangId;
|
||||
|
||||
/**
|
||||
* @brief 字符串ID类型
|
||||
*/
|
||||
typedef uint16_t StrId;
|
||||
|
||||
/**
|
||||
* @brief 字符串项结构体
|
||||
*/
|
||||
typedef struct {
|
||||
StrId id; ///< 字符串ID
|
||||
const char* str; ///< 字符串内容
|
||||
} LangStrItem;
|
||||
|
||||
/**
|
||||
* @brief 语言包结构体
|
||||
*/
|
||||
typedef struct {
|
||||
LangId id; ///< 语言ID
|
||||
const char* name; ///< 语言名称(如"English"、"中文")
|
||||
const LangStrItem* strings; ///< 字符串数组
|
||||
uint16_t string_count; ///< 字符串数量
|
||||
} LangPack;
|
||||
|
||||
/* 函数声明 ---------------------------------------------------------------------------------------------------------*/
|
||||
|
||||
#if MENU_CONFIG_ENABLE_LANG
|
||||
|
||||
/**
|
||||
* @brief 初始化多语言支持模块
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_lang_init(void);
|
||||
|
||||
/**
|
||||
* @brief 反初始化多语言支持模块
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_lang_deinit(void);
|
||||
|
||||
/**
|
||||
* @brief 设置当前语言
|
||||
* @param lang_id 语言ID
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_lang_set_current(LangId lang_id);
|
||||
|
||||
/**
|
||||
* @brief 获取当前语言ID
|
||||
* @return 当前语言ID
|
||||
*/
|
||||
LangId menu_lang_get_current(void);
|
||||
|
||||
/**
|
||||
* @brief 注册语言包
|
||||
* @param pack 语言包指针
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_lang_register_pack(const LangPack* pack);
|
||||
|
||||
/**
|
||||
* @brief 注销语言包
|
||||
* @param lang_id 语言ID
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_lang_unregister_pack(LangId lang_id);
|
||||
|
||||
/**
|
||||
* @brief 根据字符串ID获取当前语言的字符串
|
||||
* @param str_id 字符串ID
|
||||
* @return 字符串指针,未找到返回NULL
|
||||
*/
|
||||
const char* menu_lang_get_str(StrId str_id);
|
||||
|
||||
/**
|
||||
* @brief 加载语言包
|
||||
* @param pack 语言包指针
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_lang_load_pack(const LangPack* pack);
|
||||
|
||||
/**
|
||||
* @brief 查找语言包
|
||||
* @param lang_id 语言ID
|
||||
* @return 语言包指针,未找到返回NULL
|
||||
*/
|
||||
const LangPack* menu_lang_find_pack(LangId lang_id);
|
||||
|
||||
#else /* MENU_CONFIG_ENABLE_LANG */
|
||||
|
||||
/* 多语言功能未启用时的空实现 */
|
||||
#define menu_lang_init() MENU_ERR_OK
|
||||
#define menu_lang_deinit() MENU_ERR_OK
|
||||
#define menu_lang_set_current(...) MENU_ERR_OK
|
||||
#define menu_lang_get_current() 0
|
||||
#define menu_lang_register_pack(...) MENU_ERR_OK
|
||||
#define menu_lang_unregister_pack(...) MENU_ERR_OK
|
||||
#define menu_lang_get_str(id) ""
|
||||
#define menu_lang_load_pack(...) MENU_ERR_OK
|
||||
#define menu_lang_find_pack(...) NULL
|
||||
|
||||
#endif /* MENU_CONFIG_ENABLE_LANG */
|
||||
|
||||
#endif /* MENU_LANG_H */
|
||||
@ -1,397 +0,0 @@
|
||||
/**
|
||||
**********************************************************************************************************************
|
||||
* @file menu_param.c
|
||||
* @brief 菜单组件参数管理模块实现
|
||||
* @author menu_component
|
||||
* @date 2025-12-19
|
||||
**********************************************************************************************************************
|
||||
*/
|
||||
|
||||
/* Includes ----------------------------------------------------------------------------------------------------------*/
|
||||
#include "menu_param.h"
|
||||
#include "../core/menu_hash.h"
|
||||
|
||||
/* 配置参数 ---------------------------------------------------------------------------------------------------------*/
|
||||
#define MENU_PARAM_MAX_NUM 32U /* 最大参数数量 */
|
||||
|
||||
/* 全局变量 ---------------------------------------------------------------------------------------------------------*/
|
||||
static MenuParam sg_menu_params[MENU_PARAM_MAX_NUM];
|
||||
static uint16_t sg_menu_param_count = 0;
|
||||
|
||||
/* 函数实现 ---------------------------------------------------------------------------------------------------------*/
|
||||
|
||||
#if MENU_CONFIG_ENABLE_PARAM
|
||||
|
||||
/**
|
||||
* @brief 初始化参数管理模块
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_param_init(void)
|
||||
{
|
||||
// 初始化参数数组
|
||||
for (uint16_t i = 0; i < MENU_PARAM_MAX_NUM; i++) {
|
||||
sg_menu_params[i].id = 0;
|
||||
sg_menu_params[i].name = NULL;
|
||||
sg_menu_params[i].type = PARAM_TYPE_UINT8;
|
||||
sg_menu_params[i].value = NULL;
|
||||
sg_menu_params[i].default_value = NULL;
|
||||
sg_menu_params[i].min_value = NULL;
|
||||
sg_menu_params[i].max_value = NULL;
|
||||
sg_menu_params[i].is_read_only = false;
|
||||
sg_menu_params[i].is_modified = false;
|
||||
}
|
||||
|
||||
sg_menu_param_count = 0;
|
||||
return MENU_ERR_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 反初始化参数管理模块
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_param_deinit(void)
|
||||
{
|
||||
sg_menu_param_count = 0;
|
||||
return MENU_ERR_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 查找参数
|
||||
* @param id 参数ID
|
||||
* @return 参数指针,未找到返回NULL
|
||||
*/
|
||||
MenuParam* menu_param_find_internal(uint16_t id)
|
||||
{
|
||||
for (uint16_t i = 0; i < sg_menu_param_count; i++) {
|
||||
if (sg_menu_params[i].id == id) {
|
||||
return &sg_menu_params[i];
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 注册参数
|
||||
* @param id 参数ID
|
||||
* @param name 参数名称
|
||||
* @param type 参数类型
|
||||
* @param value 参数值指针
|
||||
* @param default_value 默认值指针
|
||||
* @param min_value 最小值指针
|
||||
* @param max_value 最大值指针
|
||||
* @param is_read_only 是否只读
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_param_register(uint16_t id, const char* name, ParamType type, void* value, void* default_value, void* min_value, void* max_value, bool is_read_only)
|
||||
{
|
||||
// 检查参数ID是否已存在
|
||||
if (menu_param_find_internal(id) != NULL) {
|
||||
return MENU_ERR_OPERATION_FAILED;
|
||||
}
|
||||
|
||||
// 检查参数数量是否超过最大值
|
||||
if (sg_menu_param_count >= MENU_PARAM_MAX_NUM) {
|
||||
return MENU_ERR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
// 检查参数值指针是否有效
|
||||
if (value == NULL) {
|
||||
return MENU_ERR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
// 注册参数
|
||||
MenuParam* param = &sg_menu_params[sg_menu_param_count];
|
||||
param->id = id;
|
||||
param->name = name;
|
||||
param->type = type;
|
||||
param->value = value;
|
||||
param->default_value = default_value;
|
||||
param->min_value = min_value;
|
||||
param->max_value = max_value;
|
||||
param->is_read_only = is_read_only;
|
||||
param->is_modified = false;
|
||||
|
||||
sg_menu_param_count++;
|
||||
return MENU_ERR_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 注销参数
|
||||
* @param id 参数ID
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_param_unregister(uint16_t id)
|
||||
{
|
||||
MenuParam* param = menu_param_find_internal(id);
|
||||
if (param == NULL) {
|
||||
return MENU_ERR_NODE_NOT_FOUND;
|
||||
}
|
||||
|
||||
// 查找参数在数组中的位置
|
||||
uint16_t index = 0;
|
||||
for (index = 0; index < sg_menu_param_count; index++) {
|
||||
if (sg_menu_params[index].id == id) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 将后续参数前移
|
||||
for (uint16_t i = index; i < sg_menu_param_count - 1; i++) {
|
||||
sg_menu_params[i] = sg_menu_params[i + 1];
|
||||
}
|
||||
|
||||
sg_menu_param_count--;
|
||||
return MENU_ERR_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取参数值
|
||||
* @param id 参数ID
|
||||
* @param value 用于存储参数值的指针
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_param_get_value(uint16_t id, void* value)
|
||||
{
|
||||
MenuParam* param = menu_param_find_internal(id);
|
||||
if (param == NULL) {
|
||||
return MENU_ERR_NODE_NOT_FOUND;
|
||||
}
|
||||
|
||||
if (value == NULL) {
|
||||
return MENU_ERR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
// 根据参数类型获取值
|
||||
switch (param->type) {
|
||||
case PARAM_TYPE_INT8:
|
||||
*(int8_t*)value = *(int8_t*)param->value;
|
||||
break;
|
||||
case PARAM_TYPE_UINT8:
|
||||
*(uint8_t*)value = *(uint8_t*)param->value;
|
||||
break;
|
||||
case PARAM_TYPE_INT16:
|
||||
*(int16_t*)value = *(int16_t*)param->value;
|
||||
break;
|
||||
case PARAM_TYPE_UINT16:
|
||||
*(uint16_t*)value = *(uint16_t*)param->value;
|
||||
break;
|
||||
case PARAM_TYPE_INT32:
|
||||
*(int32_t*)value = *(int32_t*)param->value;
|
||||
break;
|
||||
case PARAM_TYPE_UINT32:
|
||||
*(uint32_t*)value = *(uint32_t*)param->value;
|
||||
break;
|
||||
case PARAM_TYPE_FLOAT:
|
||||
*(float*)value = *(float*)param->value;
|
||||
break;
|
||||
default:
|
||||
return MENU_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
return MENU_ERR_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 检查参数值是否在允许范围内
|
||||
* @param param 参数指针
|
||||
* @param value 要检查的值
|
||||
* @return true为在范围内,false为超出范围
|
||||
*/
|
||||
static bool menu_param_check_range(MenuParam* param, const void* value)
|
||||
{
|
||||
if (param->min_value == NULL || param->max_value == NULL) {
|
||||
return true; // 没有设置范围限制
|
||||
}
|
||||
|
||||
switch (param->type) {
|
||||
case PARAM_TYPE_INT8:
|
||||
return (*(int8_t*)value >= *(int8_t*)param->min_value) && (*(int8_t*)value <= *(int8_t*)param->max_value);
|
||||
case PARAM_TYPE_UINT8:
|
||||
return (*(uint8_t*)value >= *(uint8_t*)param->min_value) && (*(uint8_t*)value <= *(uint8_t*)param->max_value);
|
||||
case PARAM_TYPE_INT16:
|
||||
return (*(int16_t*)value >= *(int16_t*)param->min_value) && (*(int16_t*)value <= *(int16_t*)param->max_value);
|
||||
case PARAM_TYPE_UINT16:
|
||||
return (*(uint16_t*)value >= *(uint16_t*)param->min_value) && (*(uint16_t*)value <= *(uint16_t*)param->max_value);
|
||||
case PARAM_TYPE_INT32:
|
||||
return (*(int32_t*)value >= *(int32_t*)param->min_value) && (*(int32_t*)value <= *(int32_t*)param->max_value);
|
||||
case PARAM_TYPE_UINT32:
|
||||
return (*(uint32_t*)value >= *(uint32_t*)param->min_value) && (*(uint32_t*)value <= *(uint32_t*)param->max_value);
|
||||
case PARAM_TYPE_FLOAT:
|
||||
return (*(float*)value >= *(float*)param->min_value) && (*(float*)value <= *(float*)param->max_value);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 设置参数值
|
||||
* @param id 参数ID
|
||||
* @param value 新的参数值
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_param_set_value(uint16_t id, const void* value)
|
||||
{
|
||||
MenuParam* param = menu_param_find_internal(id);
|
||||
if (param == NULL) {
|
||||
return MENU_ERR_NODE_NOT_FOUND;
|
||||
}
|
||||
|
||||
if (value == NULL) {
|
||||
return MENU_ERR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
// 检查参数是否只读
|
||||
if (param->is_read_only) {
|
||||
return MENU_ERR_OPERATION_FAILED;
|
||||
}
|
||||
|
||||
// 检查参数值是否在允许范围内
|
||||
if (!menu_param_check_range(param, value)) {
|
||||
return MENU_ERR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
// 根据参数类型设置值
|
||||
switch (param->type) {
|
||||
case PARAM_TYPE_INT8:
|
||||
if (*(int8_t*)value != *(int8_t*)param->value) {
|
||||
*(int8_t*)param->value = *(int8_t*)value;
|
||||
param->is_modified = true;
|
||||
}
|
||||
break;
|
||||
case PARAM_TYPE_UINT8:
|
||||
if (*(uint8_t*)value != *(uint8_t*)param->value) {
|
||||
*(uint8_t*)param->value = *(uint8_t*)value;
|
||||
param->is_modified = true;
|
||||
}
|
||||
break;
|
||||
case PARAM_TYPE_INT16:
|
||||
if (*(int16_t*)value != *(int16_t*)param->value) {
|
||||
*(int16_t*)param->value = *(int16_t*)value;
|
||||
param->is_modified = true;
|
||||
}
|
||||
break;
|
||||
case PARAM_TYPE_UINT16:
|
||||
if (*(uint16_t*)value != *(uint16_t*)param->value) {
|
||||
*(uint16_t*)param->value = *(uint16_t*)value;
|
||||
param->is_modified = true;
|
||||
}
|
||||
break;
|
||||
case PARAM_TYPE_INT32:
|
||||
if (*(int32_t*)value != *(int32_t*)param->value) {
|
||||
*(int32_t*)param->value = *(int32_t*)value;
|
||||
param->is_modified = true;
|
||||
}
|
||||
break;
|
||||
case PARAM_TYPE_UINT32:
|
||||
if (*(uint32_t*)value != *(uint32_t*)param->value) {
|
||||
*(uint32_t*)param->value = *(uint32_t*)value;
|
||||
param->is_modified = true;
|
||||
}
|
||||
break;
|
||||
case PARAM_TYPE_FLOAT:
|
||||
if (*(float*)value != *(float*)param->value) {
|
||||
*(float*)param->value = *(float*)value;
|
||||
param->is_modified = true;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return MENU_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
return MENU_ERR_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 恢复参数默认值
|
||||
* @param id 参数ID
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_param_restore_default(uint16_t id)
|
||||
{
|
||||
MenuParam* param = menu_param_find_internal(id);
|
||||
if (param == NULL) {
|
||||
return MENU_ERR_NODE_NOT_FOUND;
|
||||
}
|
||||
|
||||
if (param->default_value == NULL) {
|
||||
return MENU_ERR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
// 检查参数是否只读
|
||||
if (param->is_read_only) {
|
||||
return MENU_ERR_OPERATION_FAILED;
|
||||
}
|
||||
|
||||
// 恢复默认值
|
||||
return menu_param_set_value(id, param->default_value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取参数类型
|
||||
* @param id 参数ID
|
||||
* @return 参数类型
|
||||
*/
|
||||
ParamType menu_param_get_type(uint16_t id)
|
||||
{
|
||||
MenuParam* param = menu_param_find_internal(id);
|
||||
if (param != NULL) {
|
||||
return param->type;
|
||||
}
|
||||
return PARAM_TYPE_UINT8;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 检查参数是否只读
|
||||
* @param id 参数ID
|
||||
* @return true为只读,false为可写
|
||||
*/
|
||||
bool menu_param_is_read_only(uint16_t id)
|
||||
{
|
||||
MenuParam* param = menu_param_find_internal(id);
|
||||
if (param != NULL) {
|
||||
return param->is_read_only;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 检查参数是否已修改
|
||||
* @param id 参数ID
|
||||
* @return true为已修改,false为未修改
|
||||
*/
|
||||
bool menu_param_is_modified(uint16_t id)
|
||||
{
|
||||
MenuParam* param = menu_param_find_internal(id);
|
||||
if (param != NULL) {
|
||||
return param->is_modified;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 清除参数修改标记
|
||||
* @param id 参数ID
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_param_clear_modified_flag(uint16_t id)
|
||||
{
|
||||
MenuParam* param = menu_param_find_internal(id);
|
||||
if (param == NULL) {
|
||||
return MENU_ERR_NODE_NOT_FOUND;
|
||||
}
|
||||
|
||||
param->is_modified = false;
|
||||
return MENU_ERR_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 查找参数
|
||||
* @param id 参数ID
|
||||
* @return 参数指针,未找到返回NULL
|
||||
*/
|
||||
const MenuParam* menu_param_find(uint16_t id)
|
||||
{
|
||||
return menu_param_find_internal(id);
|
||||
}
|
||||
|
||||
#endif /* MENU_CONFIG_ENABLE_PARAM */
|
||||
@ -1,159 +0,0 @@
|
||||
/**
|
||||
**********************************************************************************************************************
|
||||
* @file menu_param.h
|
||||
* @brief 菜单组件参数管理模块
|
||||
* @author menu_component
|
||||
* @date 2025-12-19
|
||||
**********************************************************************************************************************
|
||||
*/
|
||||
|
||||
#ifndef MENU_PARAM_H
|
||||
#define MENU_PARAM_H
|
||||
|
||||
/* Includes ----------------------------------------------------------------------------------------------------------*/
|
||||
#include "../core/menu_config.h"
|
||||
|
||||
/* 类型定义 ---------------------------------------------------------------------------------------------------------*/
|
||||
|
||||
/**
|
||||
* @brief 参数类型
|
||||
*/
|
||||
typedef enum {
|
||||
PARAM_TYPE_INT8, /*!< 8位有符号整数 */
|
||||
PARAM_TYPE_UINT8, /*!< 8位无符号整数 */
|
||||
PARAM_TYPE_INT16, /*!< 16位有符号整数 */
|
||||
PARAM_TYPE_UINT16, /*!< 16位无符号整数 */
|
||||
PARAM_TYPE_INT32, /*!< 32位有符号整数 */
|
||||
PARAM_TYPE_UINT32, /*!< 32位无符号整数 */
|
||||
PARAM_TYPE_FLOAT, /*!< 浮点数 */
|
||||
} ParamType;
|
||||
|
||||
/**
|
||||
* @brief 参数结构体
|
||||
*/
|
||||
typedef struct {
|
||||
uint16_t id; ///< 参数ID
|
||||
const char* name; ///< 参数名称
|
||||
ParamType type; ///< 参数类型
|
||||
void* value; ///< 参数值指针
|
||||
void* default_value; ///< 默认值指针
|
||||
void* min_value; ///< 最小值指针
|
||||
void* max_value; ///< 最大值指针
|
||||
bool is_read_only; ///< 是否只读
|
||||
bool is_modified; ///< 是否已修改
|
||||
} MenuParam;
|
||||
|
||||
/* 函数声明 ---------------------------------------------------------------------------------------------------------*/
|
||||
|
||||
#if MENU_CONFIG_ENABLE_PARAM
|
||||
|
||||
/**
|
||||
* @brief 初始化参数管理模块
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_param_init(void);
|
||||
|
||||
/**
|
||||
* @brief 反初始化参数管理模块
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_param_deinit(void);
|
||||
|
||||
/**
|
||||
* @brief 注册参数
|
||||
* @param id 参数ID
|
||||
* @param name 参数名称
|
||||
* @param type 参数类型
|
||||
* @param value 参数值指针
|
||||
* @param default_value 默认值指针
|
||||
* @param min_value 最小值指针
|
||||
* @param max_value 最大值指针
|
||||
* @param is_read_only 是否只读
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_param_register(uint16_t id, const char* name, ParamType type, void* value, void* default_value, void* min_value, void* max_value, bool is_read_only);
|
||||
|
||||
/**
|
||||
* @brief 注销参数
|
||||
* @param id 参数ID
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_param_unregister(uint16_t id);
|
||||
|
||||
/**
|
||||
* @brief 获取参数值
|
||||
* @param id 参数ID
|
||||
* @param value 用于存储参数值的指针
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_param_get_value(uint16_t id, void* value);
|
||||
|
||||
/**
|
||||
* @brief 设置参数值
|
||||
* @param id 参数ID
|
||||
* @param value 新的参数值
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_param_set_value(uint16_t id, const void* value);
|
||||
|
||||
/**
|
||||
* @brief 恢复参数默认值
|
||||
* @param id 参数ID
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_param_restore_default(uint16_t id);
|
||||
|
||||
/**
|
||||
* @brief 获取参数类型
|
||||
* @param id 参数ID
|
||||
* @return 参数类型
|
||||
*/
|
||||
ParamType menu_param_get_type(uint16_t id);
|
||||
|
||||
/**
|
||||
* @brief 检查参数是否只读
|
||||
* @param id 参数ID
|
||||
* @return true为只读,false为可写
|
||||
*/
|
||||
bool menu_param_is_read_only(uint16_t id);
|
||||
|
||||
/**
|
||||
* @brief 检查参数是否已修改
|
||||
* @param id 参数ID
|
||||
* @return true为已修改,false为未修改
|
||||
*/
|
||||
bool menu_param_is_modified(uint16_t id);
|
||||
|
||||
/**
|
||||
* @brief 清除参数修改标记
|
||||
* @param id 参数ID
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_param_clear_modified_flag(uint16_t id);
|
||||
|
||||
/**
|
||||
* @brief 查找参数
|
||||
* @param id 参数ID
|
||||
* @return 参数指针,未找到返回NULL
|
||||
*/
|
||||
const MenuParam* menu_param_find(uint16_t id);
|
||||
|
||||
#else /* MENU_CONFIG_ENABLE_PARAM */
|
||||
|
||||
/* 参数管理功能未启用时的空实现 */
|
||||
#define menu_param_init() MENU_ERR_OK
|
||||
#define menu_param_deinit() MENU_ERR_OK
|
||||
#define menu_param_register(...) MENU_ERR_OK
|
||||
#define menu_param_unregister(...) MENU_ERR_OK
|
||||
#define menu_param_get_value(...) MENU_ERR_OK
|
||||
#define menu_param_set_value(...) MENU_ERR_OK
|
||||
#define menu_param_restore_default(...) MENU_ERR_OK
|
||||
#define menu_param_get_type(id) PARAM_TYPE_UINT8
|
||||
#define menu_param_is_read_only(id) false
|
||||
#define menu_param_is_modified(id) false
|
||||
#define menu_param_clear_modified_flag(...) MENU_ERR_OK
|
||||
#define menu_param_find(id) NULL
|
||||
|
||||
#endif /* MENU_CONFIG_ENABLE_PARAM */
|
||||
|
||||
#endif /* MENU_PARAM_H */
|
||||
@ -1,23 +0,0 @@
|
||||
# CMakeLists.txt for test directory
|
||||
|
||||
# 收集测试所需的源文件
|
||||
set(TEST_SOURCES
|
||||
menu_test.c
|
||||
)
|
||||
|
||||
# 创建测试可执行程序
|
||||
add_executable(menu_test ${TEST_SOURCES})
|
||||
|
||||
# 链接menu库
|
||||
target_link_libraries(menu_test PRIVATE menu)
|
||||
|
||||
# 设置包含目录
|
||||
target_include_directories(menu_test PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/..
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../src/core
|
||||
)
|
||||
|
||||
# 安装规则
|
||||
install(TARGETS menu_test
|
||||
RUNTIME DESTINATION bin
|
||||
)
|
||||
304
test/menu_test.c
304
test/menu_test.c
@ -1,304 +0,0 @@
|
||||
/**
|
||||
**********************************************************************************************************************
|
||||
* @file menu_test.c
|
||||
* @brief 菜单组件测试用例
|
||||
* @author menu_component
|
||||
* @date 2025-12-19
|
||||
**********************************************************************************************************************
|
||||
*/
|
||||
|
||||
#include "../api/menu_api.h"
|
||||
#include "../port/menu_port.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
/* 测试菜单节点ID定义 */
|
||||
#define TEST_MENU_ID_ROOT 1U
|
||||
#define TEST_MENU_ID_ITEM1 2U
|
||||
#define TEST_MENU_ID_ITEM2 3U
|
||||
#define TEST_MENU_ID_ITEM3 4U
|
||||
#define TEST_MENU_ID_SUB1 5U
|
||||
#define TEST_MENU_ID_SUB2 6U
|
||||
|
||||
/* 测试全局变量 */
|
||||
static uint32_t sg_test_tick = 0;
|
||||
static bool sg_test_callback_called = false;
|
||||
static MenuNodeId sg_test_last_node_id = 0;
|
||||
|
||||
/* 测试回调函数 */
|
||||
|
||||
static MenuErrCode test_enter_callback(MenuNodeId node_id, struct MenuCoreCtx* core_ctx)
|
||||
{
|
||||
(void)core_ctx;
|
||||
sg_test_callback_called = true;
|
||||
sg_test_last_node_id = node_id;
|
||||
printf("Callback called for node %d\n", node_id);
|
||||
return MENU_ERR_OK;
|
||||
}
|
||||
|
||||
static MenuErrCode test_exit_callback(MenuNodeId node_id, struct MenuCoreCtx* core_ctx)
|
||||
{
|
||||
(void)core_ctx;
|
||||
sg_test_callback_called = true;
|
||||
sg_test_last_node_id = node_id;
|
||||
printf("Exit callback called for node %d\n", node_id);
|
||||
return MENU_ERR_OK;
|
||||
}
|
||||
|
||||
/* 测试硬件驱动 */
|
||||
|
||||
static uint32_t test_get_tick(void)
|
||||
{
|
||||
return sg_test_tick++;
|
||||
}
|
||||
|
||||
static void test_printf(const char* fmt, va_list args)
|
||||
{
|
||||
vprintf(fmt, args);
|
||||
}
|
||||
|
||||
static MenuPortDriver sg_test_driver = {
|
||||
.printf = test_printf,
|
||||
.get_tick = test_get_tick,
|
||||
.delay_ms = NULL,
|
||||
.display = NULL,
|
||||
.key_scan = NULL,
|
||||
.irq_ctrl = NULL,
|
||||
.error_handler = NULL,
|
||||
.modbus_send = NULL,
|
||||
.modbus_receive = NULL,
|
||||
};
|
||||
|
||||
/* 测试函数 */
|
||||
|
||||
/**
|
||||
* @brief 测试菜单初始化和反初始化
|
||||
*/
|
||||
static bool test_menu_init_deinit(void)
|
||||
{
|
||||
printf("\n=== Test: Menu Init/Deinit ===\n");
|
||||
|
||||
// 初始化硬件端口
|
||||
MenuErrCode err = menu_port_init(&sg_test_driver);
|
||||
if (err != MENU_ERR_OK) {
|
||||
printf("FAIL: Failed to initialize port\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 初始化菜单
|
||||
err = menu_init();
|
||||
if (err != MENU_ERR_OK) {
|
||||
printf("FAIL: Failed to initialize menu\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 反初始化菜单
|
||||
err = menu_deinit();
|
||||
if (err != MENU_ERR_OK) {
|
||||
printf("FAIL: Failed to deinitialize menu\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 反初始化硬件端口
|
||||
err = menu_port_deinit();
|
||||
if (err != MENU_ERR_OK) {
|
||||
printf("FAIL: Failed to deinitialize port\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
printf("PASS: Menu init/deinit test passed\n");
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 测试菜单节点注册
|
||||
*/
|
||||
static bool test_menu_node_register(void)
|
||||
{
|
||||
printf("\n=== Test: Menu Node Register ===\n");
|
||||
|
||||
// 初始化
|
||||
menu_port_init(&sg_test_driver);
|
||||
menu_init();
|
||||
|
||||
// 注册根节点
|
||||
MenuNodeId root_id = menu_register_node(TEST_MENU_ID_ROOT, 0, "Root Menu", test_enter_callback, test_exit_callback);
|
||||
if (root_id != TEST_MENU_ID_ROOT) {
|
||||
printf("FAIL: Failed to register root node\n");
|
||||
menu_deinit();
|
||||
menu_port_deinit();
|
||||
return false;
|
||||
}
|
||||
printf("PASS: Root node registered\n");
|
||||
|
||||
// 注册子节点
|
||||
MenuNodeId item1_id = menu_register_node(TEST_MENU_ID_ITEM1, TEST_MENU_ID_ROOT, "Item 1", test_enter_callback, NULL);
|
||||
if (item1_id != TEST_MENU_ID_ITEM1) {
|
||||
printf("FAIL: Failed to register item 1\n");
|
||||
menu_deinit();
|
||||
menu_port_deinit();
|
||||
return false;
|
||||
}
|
||||
printf("PASS: Item 1 registered\n");
|
||||
|
||||
MenuNodeId item2_id = menu_register_node(TEST_MENU_ID_ITEM2, TEST_MENU_ID_ROOT, "Item 2", test_enter_callback, NULL);
|
||||
if (item2_id != TEST_MENU_ID_ITEM2) {
|
||||
printf("FAIL: Failed to register item 2\n");
|
||||
menu_deinit();
|
||||
menu_port_deinit();
|
||||
return false;
|
||||
}
|
||||
printf("PASS: Item 2 registered\n");
|
||||
|
||||
// 注册孙子节点
|
||||
MenuNodeId sub1_id = menu_register_node(TEST_MENU_ID_SUB1, TEST_MENU_ID_ITEM1, "Sub Item 1", test_enter_callback, NULL);
|
||||
if (sub1_id != TEST_MENU_ID_SUB1) {
|
||||
printf("FAIL: Failed to register sub item 1\n");
|
||||
menu_deinit();
|
||||
menu_port_deinit();
|
||||
return false;
|
||||
}
|
||||
printf("PASS: Sub item 1 registered\n");
|
||||
|
||||
// 清理
|
||||
menu_deinit();
|
||||
menu_port_deinit();
|
||||
|
||||
printf("PASS: Menu node register test passed\n");
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 测试菜单事件处理
|
||||
*/
|
||||
static bool test_menu_event_processing(void)
|
||||
{
|
||||
printf("\n=== Test: Menu Event Processing ===\n");
|
||||
|
||||
// 初始化
|
||||
menu_port_init(&sg_test_driver);
|
||||
menu_init();
|
||||
|
||||
// 注册测试节点
|
||||
menu_register_node(TEST_MENU_ID_ROOT, 0, "Root Menu", test_enter_callback, test_exit_callback);
|
||||
menu_register_node(TEST_MENU_ID_ITEM1, TEST_MENU_ID_ROOT, "Item 1", test_enter_callback, NULL);
|
||||
menu_register_node(TEST_MENU_ID_ITEM2, TEST_MENU_ID_ROOT, "Item 2", test_enter_callback, NULL);
|
||||
|
||||
// 重置测试状态
|
||||
sg_test_callback_called = false;
|
||||
sg_test_last_node_id = 0;
|
||||
|
||||
// 发送上键事件
|
||||
MenuErrCode err = menu_post_event(MENU_EVENT_KEY_UP, 1);
|
||||
if (err != MENU_ERR_OK) {
|
||||
printf("FAIL: Failed to post UP event\n");
|
||||
menu_deinit();
|
||||
menu_port_deinit();
|
||||
return false;
|
||||
}
|
||||
printf("PASS: UP event posted\n");
|
||||
|
||||
// 发送下键事件
|
||||
err = menu_post_event(MENU_EVENT_KEY_DOWN, 1);
|
||||
if (err != MENU_ERR_OK) {
|
||||
printf("FAIL: Failed to post DOWN event\n");
|
||||
menu_deinit();
|
||||
menu_port_deinit();
|
||||
return false;
|
||||
}
|
||||
printf("PASS: DOWN event posted\n");
|
||||
|
||||
// 处理菜单主循环
|
||||
menu_main_loop(test_get_tick());
|
||||
printf("PASS: Menu main loop processed\n");
|
||||
|
||||
// 清理
|
||||
menu_deinit();
|
||||
menu_port_deinit();
|
||||
|
||||
printf("PASS: Menu event processing test passed\n");
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 测试菜单状态查询
|
||||
*/
|
||||
static bool test_menu_state_query(void)
|
||||
{
|
||||
printf("\n=== Test: Menu State Query ===\n");
|
||||
|
||||
// 初始化
|
||||
menu_port_init(&sg_test_driver);
|
||||
menu_init();
|
||||
|
||||
// 注册测试节点
|
||||
menu_register_node(TEST_MENU_ID_ROOT, 0, "Root Menu", NULL, NULL);
|
||||
|
||||
// 查询当前状态
|
||||
MenuState state = menu_get_state();
|
||||
printf("Current state: %d\n", state);
|
||||
|
||||
// 查询当前节点
|
||||
MenuNodeId curr_node = menu_get_current_node();
|
||||
printf("Current node: %d\n", curr_node);
|
||||
|
||||
// 查询导航深度
|
||||
uint8_t depth = menu_get_nav_depth();
|
||||
printf("Navigation depth: %d\n", depth);
|
||||
|
||||
// 清理
|
||||
menu_deinit();
|
||||
menu_port_deinit();
|
||||
|
||||
printf("PASS: Menu state query test passed\n");
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 主测试函数
|
||||
*/
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
(void)argc;
|
||||
(void)argv;
|
||||
|
||||
printf("Menu Component Test Suite\n");
|
||||
printf("========================\n");
|
||||
|
||||
int test_count = 0;
|
||||
int pass_count = 0;
|
||||
|
||||
// 运行所有测试
|
||||
test_count++;
|
||||
if (test_menu_init_deinit()) {
|
||||
pass_count++;
|
||||
}
|
||||
|
||||
test_count++;
|
||||
if (test_menu_node_register()) {
|
||||
pass_count++;
|
||||
}
|
||||
|
||||
test_count++;
|
||||
if (test_menu_event_processing()) {
|
||||
pass_count++;
|
||||
}
|
||||
|
||||
test_count++;
|
||||
if (test_menu_state_query()) {
|
||||
pass_count++;
|
||||
}
|
||||
|
||||
// 打印测试结果
|
||||
printf("\n========================\n");
|
||||
printf("Test Results: %d/%d tests passed\n", pass_count, test_count);
|
||||
printf("========================\n");
|
||||
|
||||
if (pass_count == test_count) {
|
||||
printf("All tests passed!\n");
|
||||
return EXIT_SUCCESS;
|
||||
} else {
|
||||
printf("Some tests failed!\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
1794
tools/menu_config.yaml
Normal file
1794
tools/menu_config.yaml
Normal file
File diff suppressed because it is too large
Load Diff
543
开发文档.md
543
开发文档.md
@ -1,543 +0,0 @@
|
||||
# 工业级嵌入式菜单组件开发文档
|
||||
|
||||
## 1. 项目概述
|
||||
|
||||
本项目是一个基于C语言开发的工业级嵌入式菜单组件,设计用于资源受限的嵌入式系统,具有高可移植性、模块化、可裁剪、低资源占用和事件驱动解耦等核心特点。
|
||||
|
||||
## 2. 架构设计
|
||||
|
||||
### 2.1 分层架构
|
||||
|
||||
项目采用分层架构设计,将硬件与业务逻辑彻底解耦,便于移植和扩展:
|
||||
|
||||
| 层级 | 主要职责 | 包含模块 | 文件位置 | 用户可定制性 |
|
||||
|------|----------|----------|----------|--------------|
|
||||
| **核心层** | 提供菜单组件的核心功能,包括菜单节点管理、事件处理、状态机等 | 菜单核心(menu_core.c)、事件队列(menu_event.c)、哈希表索引(menu_hash.c)、栈管理(menu_stack.c) | src/core/ | 不可定制 |
|
||||
| **功能扩展层** | 基于核心层扩展的功能模块,用户可根据需求裁剪 | 参数管理(menu_param.c)、多语言支持(menu_lang.c) | src/param/、src/lang/ | 部分可定制 |
|
||||
| **硬件端口层** | 提供硬件适配接口,对接具体硬件平台 | 硬件驱动抽象(menu_port.c) | port/ | 完全可定制 |
|
||||
| **API层** | 对外提供统一的API接口,屏蔽内部实现细节 | 统一API接口(menu_api.c) | api/ | 不可定制 |
|
||||
|
||||
### 2.2 模块间依赖关系
|
||||
|
||||
```
|
||||
+------------------+
|
||||
| 用户应用层 |
|
||||
+------------------+
|
||||
↓
|
||||
+------------------+
|
||||
| API层 |
|
||||
+------------------+
|
||||
↓
|
||||
+------------------+
|
||||
| 功能扩展层 |
|
||||
+------------------+
|
||||
↓
|
||||
+------------------+
|
||||
| 核心层 |
|
||||
+------------------+
|
||||
↓
|
||||
+------------------+
|
||||
| 硬件端口层 |
|
||||
+------------------+
|
||||
↓
|
||||
+------------------+
|
||||
| 硬件平台 |
|
||||
+------------------+
|
||||
```
|
||||
|
||||
## 3. 核心思想
|
||||
|
||||
### 3.1 无动态内存分配
|
||||
|
||||
为了保证系统的稳定性和可靠性,避免内存碎片问题,项目采用了完全静态的内存管理方式:
|
||||
|
||||
- 所有菜单节点、事件队列、哈希表等均使用静态数组分配
|
||||
- 提供内存大小查询接口,便于用户根据实际需求调整内存配置
|
||||
- 每个模块都有明确的内存占用上限,便于资源规划
|
||||
|
||||
### 3.2 事件驱动解耦
|
||||
|
||||
采用事件驱动设计,将硬件输入与软件逻辑解耦,提高系统响应性:
|
||||
|
||||
- 硬件事件通过事件队列异步处理,避免阻塞主程序
|
||||
- 支持多优先级事件处理,保证关键事件优先处理
|
||||
- 事件超时机制,避免无效事件占用系统资源
|
||||
|
||||
### 3.3 高效节点查找
|
||||
|
||||
为了提高菜单节点的查找效率,项目采用了哈希表索引技术:
|
||||
|
||||
- 将节点查找时间复杂度从O(n)优化到O(1)
|
||||
- 采用优化的哈希函数,减少哈希冲突
|
||||
- 支持动态注册和注销菜单节点,哈希表自动更新
|
||||
|
||||
### 3.4 可扩展状态机
|
||||
|
||||
设计了灵活的状态机框架,支持动态扩展状态转换规则:
|
||||
|
||||
- 内置基础状态转换规则,覆盖常见菜单操作
|
||||
- 支持动态注册和注销自定义状态转换规则
|
||||
- 状态转换动作可定制,便于扩展菜单行为
|
||||
- 支持状态切换和状态查询,便于外部系统集成
|
||||
|
||||
### 3.5 模块化设计
|
||||
|
||||
采用模块化设计,各功能模块独立,便于维护和扩展:
|
||||
|
||||
- 每个模块都有清晰的接口定义,模块间低耦合
|
||||
- 支持通过宏开关裁剪不需要的功能,减少资源占用
|
||||
- 模块化的硬件适配层,便于移植到不同硬件平台
|
||||
|
||||
## 4. 核心数据结构
|
||||
|
||||
### 4.1 菜单节点结构体
|
||||
|
||||
```c
|
||||
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; ///< 是否被选中
|
||||
unsigned int reserved : 6; ///< 保留位
|
||||
} flags;
|
||||
#if MENU_CONFIG_ENABLE_PARAM
|
||||
uint16_t param_id; ///< 绑定的参数ID(启用参数时有效)
|
||||
#endif
|
||||
} MenuNode;
|
||||
```
|
||||
|
||||
**设计亮点**:
|
||||
- 使用索引代替指针,减少内存占用(32位系统节省12字节/节点,64位系统节省28字节/节点)
|
||||
- 采用双向链表结构,便于兄弟节点间遍历
|
||||
- 使用哈希表索引,提高节点查找效率
|
||||
- 位域设计,减少内存占用
|
||||
|
||||
### 4.2 菜单核心上下文
|
||||
|
||||
```c
|
||||
typedef struct {
|
||||
MenuNode nodes[MENU_CONFIG_MAX_NODES]; ///< 静态菜单节点池(无动态分配)
|
||||
MenuNode* 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; ///< 上次刷新时间(ms)
|
||||
bool is_initialized; ///< 是否已初始化
|
||||
MenuErrCode error_code; ///< 当前错误码
|
||||
const char* error_msg; ///< 当前错误信息
|
||||
uint16_t node_count; ///< 已注册节点数量
|
||||
uint16_t free_node_count; ///< 空闲节点数量
|
||||
// 状态机扩展
|
||||
#if MENU_CONFIG_ENABLE_STATE_MACHINE_EXT
|
||||
MenuStateTransition custom_transitions[MENU_CONFIG_MAX_STATE_TRANSITIONS]; ///< 自定义状态转换规则
|
||||
uint16_t custom_transition_count; ///< 自定义状态转换规则数量
|
||||
#endif
|
||||
} MenuCoreCtx;
|
||||
```
|
||||
|
||||
**设计亮点**:
|
||||
- 集中管理菜单组件的所有状态和资源
|
||||
- 静态数组设计,无动态内存分配
|
||||
- 包含所有核心数据结构,便于统一管理
|
||||
- 支持状态机扩展,便于功能扩展
|
||||
|
||||
### 4.3 事件队列结构体
|
||||
|
||||
```c
|
||||
typedef struct {
|
||||
MenuEvent buffer[MENU_CONFIG_EVENT_QUEUE_LEN]; ///< 队列缓冲区
|
||||
uint8_t head; ///< 入队指针
|
||||
uint8_t tail; ///< 出队指针
|
||||
uint8_t count; ///< 队列元素数量
|
||||
uint8_t reserved; ///< 保留字节,用于对齐
|
||||
} MenuEventQueue;
|
||||
```
|
||||
|
||||
**设计亮点**:
|
||||
- 环形队列设计,避免内存碎片
|
||||
- 支持多优先级事件处理
|
||||
- 队列满时自动替换低优先级事件,保证高优先级事件优先处理
|
||||
- 事件超时机制,避免无效事件占用资源
|
||||
|
||||
### 4.4 状态转换结构体
|
||||
|
||||
```c
|
||||
typedef struct {
|
||||
MenuState current_state; ///< 当前状态
|
||||
MenuEventType event; ///< 触发事件
|
||||
MenuState next_state; ///< 下一个状态
|
||||
MenuErrCode (*action)(MenuGlobalCtx* global_ctx); ///< 状态转换动作
|
||||
const char* desc; ///< 状态转换描述(用于调试)
|
||||
} MenuStateTransition;
|
||||
```
|
||||
|
||||
**设计亮点**:
|
||||
- 灵活的状态转换规则,支持动态注册
|
||||
- 状态转换动作可定制,便于扩展菜单行为
|
||||
- 支持状态转换描述,便于调试
|
||||
|
||||
### 4.5 参数结构体
|
||||
|
||||
```c
|
||||
typedef struct {
|
||||
uint16_t id; ///< 参数ID
|
||||
const char* name; ///< 参数名称
|
||||
ParamType type; ///< 参数类型
|
||||
void* value; ///< 参数值指针
|
||||
void* default_value; ///< 默认值指针
|
||||
void* min_value; ///< 最小值指针
|
||||
void* max_value; ///< 最大值指针
|
||||
bool is_read_only; ///< 是否只读
|
||||
bool is_modified; ///< 是否已修改
|
||||
} MenuParam;
|
||||
```
|
||||
|
||||
**设计亮点**:
|
||||
- 支持多种参数类型(int8/uint8/int16/uint16/int32/uint32/float)
|
||||
- 自动范围检查和边界处理
|
||||
- 参数与菜单节点绑定,支持菜单直接调整参数
|
||||
- 支持默认值恢复功能
|
||||
|
||||
### 4.6 语言包结构体
|
||||
|
||||
```c
|
||||
typedef struct {
|
||||
LangId id; ///< 语言ID
|
||||
const char* name; ///< 语言名称(如"English"、"中文")
|
||||
const LangStrItem* strings; ///< 字符串数组
|
||||
uint16_t string_count; ///< 字符串数量
|
||||
} LangPack;
|
||||
```
|
||||
|
||||
**设计亮点**:
|
||||
- 支持多种语言切换
|
||||
- 字符串ID映射机制,便于管理和维护
|
||||
- 自动回退到默认语言,提高系统容错性
|
||||
- 优化的语言字符串查找算法
|
||||
|
||||
## 5. 核心算法
|
||||
|
||||
### 5.1 哈希表算法
|
||||
|
||||
**功能**:用于快速查找菜单节点,将节点查找时间复杂度从O(n)优化到O(1)
|
||||
|
||||
**实现原理**:
|
||||
|
||||
```c
|
||||
static uint16_t menu_core_hash_func(MenuNodeId node_id)
|
||||
{
|
||||
// 优化后的哈希函数:使用混合哈希算法,减少冲突,提高分布均匀性
|
||||
// 结合了旋转和异或操作,适合嵌入式系统的高效计算
|
||||
uint32_t hash = node_id;
|
||||
|
||||
// 旋转和异或操作,提高哈希分布的均匀性
|
||||
hash = (hash << 15) | (hash >> 17); // 旋转15位
|
||||
hash ^= 0x61C88647; // 黄金比例常数,提高随机性
|
||||
hash *= 0x85EBca6B; // 另一个黄金比例常数
|
||||
hash = (hash << 13) | (hash >> 19); // 旋转13位
|
||||
hash ^= 0xC2B2AE35; // 再一次异或黄金比例常数
|
||||
|
||||
return (uint16_t)(hash % MENU_CONFIG_HASH_TABLE_SIZE);
|
||||
}
|
||||
```
|
||||
|
||||
**设计亮点**:
|
||||
- 采用优化的混合哈希算法,减少冲突
|
||||
- 结合旋转和异或操作,提高哈希分布的均匀性
|
||||
- 适合嵌入式系统的高效计算,执行速度快
|
||||
- 支持动态添加和删除节点,哈希表自动更新
|
||||
|
||||
### 5.2 事件队列算法
|
||||
|
||||
**功能**:实现基于优先级的事件处理机制,支持多优先级事件,队列满时自动替换低优先级事件
|
||||
|
||||
**实现原理**:
|
||||
- 采用环形队列设计,避免内存碎片
|
||||
- 支持4级事件优先级(低、普通、高、紧急)
|
||||
- 同优先级事件采用FIFO顺序处理
|
||||
- 队列满时,低优先级事件会被新的高优先级事件替换
|
||||
- 事件包含时间戳,支持超时处理
|
||||
|
||||
**关键函数**:
|
||||
- `menu_post_event`:发送事件到事件队列
|
||||
- `menu_core_get_event`:从事件队列获取下一个待处理事件
|
||||
- `menu_main_loop`:菜单主循环,处理事件和刷新显示
|
||||
|
||||
### 5.3 状态机算法
|
||||
|
||||
**功能**:实现菜单的状态管理和状态转换,支持动态注册自定义状态转换规则
|
||||
|
||||
**实现原理**:
|
||||
- 基于有限状态机(FSM)设计,定义了菜单的基本状态和状态转换规则
|
||||
- 支持动态注册和注销自定义状态转换规则
|
||||
- 状态转换时执行相应的动作函数,便于扩展菜单行为
|
||||
- 支持状态查询和状态切换
|
||||
|
||||
**内置状态**:
|
||||
- `MENU_STATE_INIT`:初始化状态
|
||||
- `MENU_STATE_NORMAL`:正常导航状态
|
||||
- `MENU_STATE_PARAM_EDIT`:参数编辑状态
|
||||
- `MENU_STATE_CONFIRM`:确认状态
|
||||
- `MENU_STATE_ERROR`:错误状态
|
||||
|
||||
**关键函数**:
|
||||
- `menu_state_register_transition`:注册自定义状态转换规则
|
||||
- `menu_state_unregister_transition`:注销自定义状态转换规则
|
||||
- `menu_state_get_current`:获取当前状态
|
||||
- `menu_state_switch`:切换到指定状态
|
||||
- `menu_state_transition`:处理状态转换
|
||||
|
||||
### 5.4 菜单导航算法
|
||||
|
||||
**功能**:实现菜单的上下左右导航,支持多级菜单和栈管理
|
||||
|
||||
**实现原理**:
|
||||
- 使用栈数据结构管理导航层级
|
||||
- 支持环形导航(到达边界自动循环)
|
||||
- 记录完整的导航路径,便于回溯
|
||||
- 支持导航深度查询和路径获取
|
||||
|
||||
**关键函数**:
|
||||
- `menu_core_navigate_enter`:进入子菜单
|
||||
- `menu_core_navigate_back`:返回上一级菜单
|
||||
- `menu_core_navigate_up`:上移菜单项
|
||||
- `menu_core_navigate_down`:下移菜单项
|
||||
- `menu_get_nav_path`:获取导航路径
|
||||
- `menu_get_nav_depth`:获取导航深度
|
||||
|
||||
## 6. 核心功能模块
|
||||
|
||||
### 6.1 菜单节点管理
|
||||
|
||||
**功能**:提供菜单节点的注册、删除、更新和查找功能
|
||||
|
||||
**实现原理**:
|
||||
- 使用静态数组存储菜单节点,无动态内存分配
|
||||
- 采用哈希表索引,提高节点查找效率
|
||||
- 支持批量注册菜单节点,提高初始化效率
|
||||
- 支持动态更新和删除菜单节点
|
||||
|
||||
**关键函数**:
|
||||
- `menu_register_node`:注册单个菜单节点
|
||||
- `menu_register_nodes`:批量注册菜单节点
|
||||
- `menu_unregister_node`:删除菜单节点
|
||||
- `menu_update_node`:更新菜单节点
|
||||
- `menu_core_find_node`:查找菜单节点
|
||||
|
||||
### 6.2 参数管理
|
||||
|
||||
**功能**:提供参数的注册、读写、范围检查和默认值恢复功能
|
||||
|
||||
**实现原理**:
|
||||
- 支持多种参数类型(int8/uint8/int16/uint16/int32/uint32/float)
|
||||
- 自动范围检查和边界处理
|
||||
- 参数与菜单节点绑定,支持菜单直接调整参数
|
||||
- 支持默认值恢复功能
|
||||
|
||||
**关键函数**:
|
||||
- `menu_param_register`:注册参数
|
||||
- `menu_param_set_value`:设置参数值
|
||||
- `menu_param_get_value`:获取参数值
|
||||
- `menu_param_get_type`:获取参数类型
|
||||
|
||||
### 6.3 多语言支持
|
||||
|
||||
**功能**:提供多语言切换和字符串管理功能
|
||||
|
||||
**实现原理**:
|
||||
- 支持多种语言切换
|
||||
- 字符串ID映射机制,便于管理和维护
|
||||
- 自动回退到默认语言,提高系统容错性
|
||||
- 优化的语言字符串查找算法
|
||||
|
||||
**关键函数**:
|
||||
- `menu_lang_set_current`:设置当前语言
|
||||
- `menu_lang_get_current`:获取当前语言
|
||||
- `menu_lang_register_str`:注册语言字符串
|
||||
- `menu_lang_load_pack`:加载语言包
|
||||
|
||||
### 6.4 Modbus映射
|
||||
|
||||
**功能**:实现参数与Modbus寄存器的双向映射
|
||||
|
||||
**实现原理**:
|
||||
- 支持多种Modbus寄存器类型(线圈、离散输入、保持寄存器、输入寄存器)
|
||||
- 支持多种字节序(小端、大端、Modbus标准)
|
||||
- 自动类型转换和边界检查
|
||||
- 灵活的读写权限控制
|
||||
|
||||
**关键函数**:
|
||||
- `menu_modbus_map_register`:注册参数与Modbus寄存器的映射关系
|
||||
- `menu_modbus_map_param_to_reg`:参数值转换为Modbus寄存器数据
|
||||
- `menu_modbus_map_reg_to_param`:Modbus寄存器数据转换为参数值
|
||||
|
||||
## 7. 硬件适配层
|
||||
|
||||
**功能**:提供硬件适配接口,对接具体硬件平台
|
||||
|
||||
**实现原理**:
|
||||
- 定义统一的硬件驱动结构体,包含各种硬件接口
|
||||
- 支持运行时配置硬件驱动,便于动态切换
|
||||
- 屏蔽不同硬件平台的差异,提高组件的可移植性
|
||||
|
||||
**硬件接口**:
|
||||
|
||||
```c
|
||||
typedef struct {
|
||||
void (*printf)(const char* fmt, va_list args); ///< 硬件打印接口
|
||||
uint32_t (*get_tick)(void); ///< 获取系统滴答时间
|
||||
void (*delay_ms)(uint32_t ms); ///< 硬件延迟函数
|
||||
void (*display)(const char* menu_name, uint16_t menu_id); ///< 菜单显示接口
|
||||
MenuEventType (*key_scan)(void); ///< 按键扫描接口(可选)
|
||||
void (*irq_ctrl)(bool enable); ///< 中断管理接口(可选)
|
||||
void (*error_handler)(MenuErrCode err_code); ///< 错误处理接口(可选)
|
||||
MenuErrCode (*modbus_send)(ModbusRegType reg_type, uint16_t reg_addr, const uint8_t* reg_buf, uint8_t buf_len); ///< Modbus发送接口(可选)
|
||||
MenuErrCode (*modbus_receive)(ModbusRegType reg_type, uint16_t reg_addr, uint8_t* reg_buf, uint8_t buf_len); ///< Modbus接收接口(可选)
|
||||
} MenuPortDriver;
|
||||
```
|
||||
|
||||
**关键函数**:
|
||||
- `menu_port_init`:硬件端口初始化
|
||||
- `menu_port_deinit`:硬件端口反初始化
|
||||
|
||||
## 8. 配置管理
|
||||
|
||||
**功能**:提供灵活的配置选项,便于用户根据需求定制功能
|
||||
|
||||
**实现原理**:
|
||||
- 所有配置项集中在`menu_config.h`文件中
|
||||
- 支持通过宏开关裁剪不需要的功能
|
||||
- 支持调整资源大小,如最大节点数、栈深度、事件队列长度等
|
||||
- 支持调试开关,便于开发和调试
|
||||
|
||||
**核心配置项**:
|
||||
|
||||
| 配置项 | 说明 | 默认值 |
|
||||
|-------|------|-------|
|
||||
| MENU_CONFIG_MAX_NODES | 最大菜单节点数 | 32 |
|
||||
| MENU_CONFIG_HASH_TABLE_SIZE | 菜单哈希表大小(必须为质数) | 31 |
|
||||
| MENU_CONFIG_STACK_DEPTH | 菜单栈深度 | 8 |
|
||||
| MENU_CONFIG_EVENT_QUEUE_LEN | 事件队列长度 | 16 |
|
||||
| MENU_CONFIG_ENABLE_ASSERT | 是否启用断言 | 1 |
|
||||
| MENU_CONFIG_ENABLE_DEBUG | 是否启用调试打印 | 1 |
|
||||
| MENU_CONFIG_ENABLE_MEM_MONITOR | 是否启用内存监控 | 1 |
|
||||
| MENU_CONFIG_ENABLE_PARAM | 是否启用参数管理 | 1 |
|
||||
| MENU_CONFIG_ENABLE_LANG | 是否启用多语言支持 | 1 |
|
||||
| MENU_CONFIG_ENABLE_MODBUS_MAP | 是否启用Modbus映射 | 1 |
|
||||
|
||||
## 9. 工业级设计考量
|
||||
|
||||
### 9.1 可靠性设计
|
||||
|
||||
- **无动态内存分配**:完全使用静态数组,避免内存碎片和内存泄漏
|
||||
- **完善的错误处理**:每个函数都有明确的错误码返回,便于调试和错误定位
|
||||
- **边界检查**:所有数组访问都有边界检查,防止越界访问
|
||||
- **断言机制**:关键函数入口有断言检查,便于调试
|
||||
- **事件超时处理**:避免无效事件占用系统资源
|
||||
|
||||
### 9.2 性能优化
|
||||
|
||||
- **高效的哈希表算法**:将节点查找时间复杂度从O(n)优化到O(1)
|
||||
- **位域设计**:减少内存占用,提高缓存命中率
|
||||
- **索引代替指针**:减少内存占用,提高系统兼容性
|
||||
- **事件驱动设计**:提高系统响应性,减少CPU占用
|
||||
- **模块化设计**:支持功能裁剪,减少资源占用
|
||||
|
||||
### 9.3 可移植性设计
|
||||
|
||||
- **硬件抽象层**:屏蔽不同硬件平台的差异,提高组件的可移植性
|
||||
- **标准C语言实现**:不依赖特定的编译器或操作系统
|
||||
- **灵活的配置选项**:支持根据不同硬件平台调整资源大小
|
||||
- **模块化设计**:各功能模块独立,便于移植和扩展
|
||||
|
||||
### 9.4 可维护性设计
|
||||
|
||||
- **清晰的代码结构**:采用分层架构和模块化设计,代码结构清晰
|
||||
- **完善的文档**:提供详细的API文档和开发文档
|
||||
- **统一的代码风格**:采用清晰的命名规范和注释风格
|
||||
- **调试支持**:提供丰富的调试打印和日志功能
|
||||
- **错误定位机制**:每个错误都有明确的错误码和错误信息
|
||||
|
||||
## 10. 应用场景
|
||||
|
||||
本菜单组件适用于各种资源受限的嵌入式系统,特别是工业控制领域,如:
|
||||
|
||||
- 工业控制器
|
||||
- 智能家居设备
|
||||
- 仪器仪表
|
||||
- 医疗设备
|
||||
- 嵌入式人机界面
|
||||
- Modbus从站设备
|
||||
|
||||
## 11. 开发流程
|
||||
|
||||
### 11.1 硬件适配
|
||||
|
||||
1. 根据目标硬件平台,实现`menu_port.c`中的硬件接口
|
||||
2. 配置`menu_config.h`中的资源大小和功能开关
|
||||
3. 编译和测试硬件适配层
|
||||
|
||||
### 11.2 菜单设计
|
||||
|
||||
1. 设计菜单结构,确定菜单节点的层级关系
|
||||
2. 注册菜单节点,构建菜单树
|
||||
3. 注册参数(可选)
|
||||
4. 注册Modbus映射(可选)
|
||||
5. 注册自定义状态转换规则(可选)
|
||||
|
||||
### 11.3 应用集成
|
||||
|
||||
1. 在应用程序中初始化菜单组件
|
||||
2. 在主循环中调用`menu_main_loop`处理事件
|
||||
3. 调用`menu_post_event`发送硬件事件(如按键事件)
|
||||
4. 根据需要调用其他API接口,实现自定义功能
|
||||
|
||||
## 12. 测试与验证
|
||||
|
||||
### 12.1 单元测试
|
||||
|
||||
项目提供了单元测试框架,位于`test/menu_test.c`,包含以下测试用例:
|
||||
|
||||
- 菜单初始化和反初始化测试
|
||||
- 菜单节点注册和删除测试
|
||||
- 菜单导航测试
|
||||
- 事件队列测试
|
||||
- 参数管理测试
|
||||
- 多语言支持测试
|
||||
- Modbus映射测试
|
||||
|
||||
### 12.2 集成测试
|
||||
|
||||
- 硬件适配测试:验证硬件接口的正确性
|
||||
- 功能测试:验证各个功能模块的正确性
|
||||
- 性能测试:测试菜单组件的执行效率和资源占用
|
||||
- 可靠性测试:测试菜单组件在各种异常情况下的表现
|
||||
|
||||
### 12.3 现场测试
|
||||
|
||||
- 在目标硬件平台上进行测试,验证菜单组件的实际运行效果
|
||||
- 测试各种用户操作场景,确保菜单组件的易用性和稳定性
|
||||
- 测试长时间运行情况下的稳定性
|
||||
|
||||
## 13. 版本管理
|
||||
|
||||
| 版本 | 发布日期 | 主要变更 |
|
||||
|------|----------|----------|
|
||||
| v1.0.0 | 2025-12-19 | 初始版本,包含核心功能 |
|
||||
|
||||
## 14. 总结
|
||||
|
||||
本项目是一个设计精良的工业级嵌入式菜单组件,具有高可靠性、高性能、高可移植性和高可扩展性等特点。采用分层架构、事件驱动设计、哈希表索引、可扩展状态机等先进技术,适合各种资源受限的嵌入式系统。
|
||||
|
||||
通过本开发文档,开发者可以全面了解菜单组件的架构设计、核心思想、数据算法和使用方法,便于快速集成和定制开发。
|
||||
Reference in New Issue
Block a user