整定一版

This commit is contained in:
冯佳
2025-12-19 17:01:27 +08:00
parent 294a49f207
commit 8bddc34c88
43 changed files with 7273 additions and 956 deletions

107
src/core/menu_config.h Normal file
View File

@ -0,0 +1,107 @@
/**
**********************************************************************************************************************
* @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 */

778
src/core/menu_core.c Normal file
View File

@ -0,0 +1,778 @@
/**
**********************************************************************************************************************
* @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 节点ID0表示自动分配
* @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;
}

246
src/core/menu_core.h Normal file
View File

@ -0,0 +1,246 @@
/**
**********************************************************************************************************************
* @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 节点ID0表示自动分配
* @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 */

186
src/core/menu_event.c Normal file
View File

@ -0,0 +1,186 @@
/**
**********************************************************************************************************************
* @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);
}

76
src/core/menu_event.h Normal file
View File

@ -0,0 +1,76 @@
/**
**********************************************************************************************************************
* @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 */

141
src/core/menu_hash.c Normal file
View File

@ -0,0 +1,141 @@
/**
**********************************************************************************************************************
* @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;
}

46
src/core/menu_hash.h Normal file
View File

@ -0,0 +1,46 @@
/**
**********************************************************************************************************************
* @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 */

350
src/core/menu_permission.c Normal file
View File

@ -0,0 +1,350 @@
/**
**********************************************************************************************************************
* @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 */

View File

@ -0,0 +1,96 @@
/**
**********************************************************************************************************************
* @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 */

441
src/core/menu_persistence.c Normal file
View File

@ -0,0 +1,441 @@
/**
**********************************************************************************************************************
* @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 */

103
src/core/menu_persistence.h Normal file
View File

@ -0,0 +1,103 @@
/**
**********************************************************************************************************************
* @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 */

215
src/core/menu_stack.c Normal file
View File

@ -0,0 +1,215 @@
/**
**********************************************************************************************************************
* @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;
}
}

104
src/core/menu_stack.h Normal file
View File

@ -0,0 +1,104 @@
/**
**********************************************************************************************************************
* @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 */

233
src/core/menu_types.h Normal file
View File

@ -0,0 +1,233 @@
/**
**********************************************************************************************************************
* @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-30最高
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 */

228
src/lang/menu_lang.c Normal file
View File

@ -0,0 +1,228 @@
/**
**********************************************************************************************************************
* @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 */

125
src/lang/menu_lang.h Normal file
View File

@ -0,0 +1,125 @@
/**
**********************************************************************************************************************
* @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 */

397
src/param/menu_param.c Normal file
View File

@ -0,0 +1,397 @@
/**
**********************************************************************************************************************
* @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 */

159
src/param/menu_param.h Normal file
View File

@ -0,0 +1,159 @@
/**
**********************************************************************************************************************
* @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 */